<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 5.4.0">


  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png">
  <link rel="mask-icon" href="/images/logo.svg" color="#222">

<link rel="stylesheet" href="/css/main.css">



<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.15.2/css/all.min.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/animate.css@3.1.1/animate.min.css">

<script class="hexo-configurations">
    var NexT = window.NexT || {};
    var CONFIG = {"hostname":"littlefxc.github.io","root":"/","images":"/images","scheme":"Mist","version":"8.2.2","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12},"copycode":false,"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"fadeInDown","post_body":"fadeInDown","coll_header":"fadeInLeft","sidebar":"fadeInUp"}},"prism":false,"i18n":{"placeholder":"搜索...","empty":"没有找到任何搜索结果：${query}","hits_time":"找到 ${hits} 个搜索结果（用时 ${time} 毫秒）","hits":"找到 ${hits} 个搜索结果"},"path":"/search.xml","localsearch":{"enable":true,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false}};
  </script>
<meta property="og:type" content="website">
<meta property="og:title" content="一年春又来">
<meta property="og:url" content="http://littlefxc.github.io/index.html">
<meta property="og:site_name" content="一年春又来">
<meta property="og:locale" content="zh_CN">
<meta property="article:author" content="一年春又来">
<meta name="twitter:card" content="summary">


<link rel="canonical" href="http://littlefxc.github.io/">


<script class="page-configurations">
  // https://hexo.io/docs/variables.html
  CONFIG.page = {
    sidebar: "",
    isHome : true,
    isPost : false,
    lang   : 'zh-CN'
  };
</script>
<title>一年春又来</title>
  




  <noscript>
  <style>
  body { margin-top: 2rem; }

  .use-motion .menu-item,
  .use-motion .sidebar,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-header {
    visibility: visible;
  }

  .use-motion .header,
  .use-motion .site-brand-container .toggle,
  .use-motion .footer { opacity: initial; }

  .use-motion .site-title,
  .use-motion .site-subtitle,
  .use-motion .custom-logo-image {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line {
    transform: scaleX(1);
  }

  .search-pop-overlay, .sidebar-nav { display: none; }
  .sidebar-panel { display: block; }
  </style>
</noscript>

<link rel="alternate" href="/atom.xml" title="一年春又来" type="application/atom+xml">
</head>

<body itemscope itemtype="http://schema.org/WebPage" class="use-motion">
  <div class="headband"></div>

  <main class="main">
    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏" role="button">
        <span class="toggle-line"></span>
        <span class="toggle-line"></span>
        <span class="toggle-line"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/" class="brand" rel="start">
      <i class="logo-line"></i>
      <h1 class="site-title">一年春又来</h1>
      <i class="logo-line"></i>
    </a>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
        <i class="fa fa-search fa-fw fa-lg"></i>
    </div>
  </div>
</div>



<nav class="site-nav">
  <ul class="main-menu menu">
        <li class="menu-item menu-item-home"><a href="/" rel="section"><i class="home                          //首页 fa-fw"></i>首页</a></li>
        <li class="menu-item menu-item-archives"><a href="/archives/" rel="section"><i class="archive          //归档 fa-fw"></i>归档</a></li>
        <li class="menu-item menu-item-categories"><a href="/categories/" rel="section"><i class="th           //分类 fa-fw"></i>分类</a></li>
        <li class="menu-item menu-item-tags"><a href="/tags/" rel="section"><i class="tags                     //标签 fa-fw"></i>标签</a></li>
      <li class="menu-item menu-item-search">
        <a role="button" class="popup-trigger"><i class="fa fa-search fa-fw"></i>搜索
        </a>
      </li>
  </ul>
</nav>



  <div class="search-pop-overlay">
    <div class="popup search-popup"><div class="search-header">
  <span class="search-icon">
    <i class="fa fa-search"></i>
  </span>
  <div class="search-input-container">
    <input autocomplete="off" autocapitalize="off" maxlength="80"
           placeholder="搜索..." spellcheck="false"
           type="search" class="search-input">
  </div>
  <span class="popup-btn-close" role="button">
    <i class="fa fa-times-circle"></i>
  </span>
</div>
<div class="search-result-container no-result">
  <div class="search-result-icon">
    <i class="fa fa-spinner fa-pulse fa-5x"></i>
  </div>
</div>

    </div>
  </div>

</div>
        
  
  <div class="toggle sidebar-toggle" role="button">
    <span class="toggle-line"></span>
    <span class="toggle-line"></span>
    <span class="toggle-line"></span>
  </div>

  <aside class="sidebar">

    <div class="sidebar-inner sidebar-overview-active">
      <ul class="sidebar-nav">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <div class="sidebar-panel-container">
        <!--noindex-->
        <div class="post-toc-wrap sidebar-panel">
        </div>
        <!--/noindex-->

        <div class="site-overview-wrap sidebar-panel">
          <div class="site-author site-overview-item animated" itemprop="author" itemscope itemtype="http://schema.org/Person">
  <p class="site-author-name" itemprop="name">一年春又来</p>
  <div class="site-description" itemprop="description"></div>
</div>
<div class="site-state-wrap site-overview-item animated">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
          <a href="/archives/">
        
          <span class="site-state-item-count">234</span>
          <span class="site-state-item-name">日志</span>
        </a>
      </div>
      <div class="site-state-item site-state-categories">
            <a href="/categories/">
          
        <span class="site-state-item-count">38</span>
        <span class="site-state-item-name">分类</span></a>
      </div>
      <div class="site-state-item site-state-tags">
            <a href="/tags/">
          
        <span class="site-state-item-count">125</span>
        <span class="site-state-item-name">标签</span></a>
      </div>
  </nav>
</div>



        </div>
      </div>
    </div>
  </aside>
  <div class="sidebar-dimmer"></div>


    </header>

    
  <div class="back-to-top" role="button">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>

<noscript>
  <div class="noscript-warning">Theme NexT works best with JavaScript enabled</div>
</noscript>


    <div class="main-inner index posts-expand">

    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
    <link itemprop="mainEntityOfPage" href="http://littlefxc.github.io/2022/05/07/docs/%E5%BE%AE%E6%9C%8D%E5%8A%A1/%E6%9C%8D%E5%8A%A1%E8%B0%83%E7%94%A8%E9%93%BE%E8%B7%AF%E8%BF%BD%E8%B8%AA/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="一年春又来">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="一年春又来">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/2022/05/07/docs/%E5%BE%AE%E6%9C%8D%E5%8A%A1/%E6%9C%8D%E5%8A%A1%E8%B0%83%E7%94%A8%E9%93%BE%E8%B7%AF%E8%BF%BD%E8%B8%AA/" class="post-title-link" itemprop="url">服务调用链路追踪</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">发表于</span>

      <time title="创建时间：2022-05-07 06:32:08" itemprop="dateCreated datePublished" datetime="2022-05-07T06:32:08+08:00">2022-05-07</time>
    </span>
      <span class="post-meta-item">
        <span class="post-meta-item-icon">
          <i class="far fa-calendar-check"></i>
        </span>
        <span class="post-meta-item-text">更新于</span>
        <time title="修改时间：2022-05-10 09:26:59" itemprop="dateModified" datetime="2022-05-10T09:26:59+08:00">2022-05-10</time>
      </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">分类于</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F/" itemprop="url" rel="index"><span itemprop="name">分布式系统</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><ul>
<li><p>服务调用链追踪是干什么的？</p>
</li>
<li><p>Sleuth 核心功能和体系结构？</p>
<p>调用链路数据模型 - Trace，Span，Annotation</p>
</li>
<li><p>链路追踪原理介绍</p>
</li>
<li><p>Zipkin 简介：搭建Zipkin服务端、Sleuth 集成 Zipkin</p>
</li>
<li><p>Sleuth 集成 ELK 实现日志搜索</p>
</li>
</ul>
<h2 id="链路追踪的基本功能"><a href="#链路追踪的基本功能" class="headerlink" title="链路追踪的基本功能"></a>链路追踪的基本功能</h2><ul>
<li>分布式环境下链路追踪</li>
<li>Timing 信息</li>
<li>定位链路</li>
<li>信息收集和展示</li>
</ul>
<h2 id="Sleuth"><a href="#Sleuth" class="headerlink" title="Sleuth"></a>Sleuth</h2><h3 id="Sleuth-的功能"><a href="#Sleuth-的功能" class="headerlink" title="Sleuth 的功能"></a>Sleuth 的功能</h3><p>Sleuth 的最核心功能就是提供链路追踪，在一个用户请求发起到结束的整个过程中，这个Request经过的所有服务都会被梳理出来：</p>
<p><img src="https://gitee.com/littlefxc/oss/raw/master/images/1315495-20210124092812074-1348764625.png"></p>
<p>上图是一个由用户 X 请求发起的，穿过多个服务的分布式系统，A、B、C、D、E 表示不同的子系统或处理过程。在这个图中， A 是前端，B、C 是中间层、D、E 是 C 的后端。这些子系统通过 rpc 协议连接，例如 gRPC。</p>
<p>一个简单实用的分布式链路追踪系统的实现，就是对服务器上每一次请求以及响应收集跟踪标识符(message identifiers)和时间戳(timestamped events)。</p>
<p>分布式服务的跟踪系统需要记录在一次特定的请求后系统中完成的所有工作的信息。用户请求可以是并行的，同一时间可能有大量的动作要处理，一个请求也会经过系统中的多个服务，系统中时时刻刻都在产生各种跟踪信息，必须将一个请求在不同服务中产生的追踪信息关联起来。</p>
<p>借助 Sleuth 的链路追踪能力，我们还可以完成一些其他的任务，比如说：</p>
<ol>
<li><strong>线上故障定位</strong>：结合Tracking ID寻找上下游链路中所有的日志信息（这一步还需要借助一些其他开源组件，后面会有这部分的Demo）</li>
<li><strong>依赖分析梳理</strong>：梳理上下游依赖关系，理清整个系统中所有微服务之间的依赖关系</li>
<li><strong>链路优化</strong>：比如说目前我们有三种途径可以导流到下单接口，通过对链路调用情况的统计分析，我们可以识别出转化率最高的业务场景，从而为以后的产品设计提供指导意见。</li>
<li><strong>性能分析</strong>：梳理各个环节的时间消耗，找到性能瓶颈，为性能优化、软硬件资源调配指明方向</li>
</ol>
<h3 id="Sleuth-的设计理念"><a href="#Sleuth-的设计理念" class="headerlink" title="Sleuth 的设计理念"></a>Sleuth 的设计理念</h3><p><img src="https://gitee.com/littlefxc/oss/raw/master/images/5e13fe4a0933db6c28121464.png"></p>
<p>从上图我们可以看出，Sleuth 采用底层 Log 系统的方式实现业务埋点。</p>
<h3 id="哪些数据需要埋点？"><a href="#哪些数据需要埋点？" class="headerlink" title="哪些数据需要埋点？"></a><strong>哪些数据需要埋点？</strong></h3><p>每一个微服务都有自己的Log组件（slf4j，lockback等各不相同），当我们集成了Sleuth之后，它便会将链路信息传递给底层Log组件，同时Log组件会在每行Log的头部输出这些数据，这个埋点动作主要会记录两个关键信息：</p>
<ul>
<li><strong>链路ID</strong>： 当前调用链的唯一ID，在这次调用请求开始到结束的过程中，所有经过的节点都拥有一个相同的链路ID</li>
<li><strong>单元ID</strong>： 在一次链路调用中会访问不同服务器节点上的服务，每一次服务调用都相当于一个独立单元，也就是说会有一个独立的单元ID。同时每一个独立单元都要知道调用请求来自哪里（就是对当前服务发起直接调用的那一方的单元ID，我们记为Parent ID）</li>
</ul>
<p>比如这里服务A是起始节点，所以它的Event ID（单元ID）和Trace ID（链路ID）相同，而服务B的前置节点就是A节点，所以B的Parent Event就指向A的Event ID。而C在B的下游，所以C的Parent就指向B。A、B和C三个服务都有同一个链路ID，但是各自有不同的单元ID。</p>
<h3 id="数据埋点之前要解决的问题"><a href="#数据埋点之前要解决的问题" class="headerlink" title="数据埋点之前要解决的问题"></a>数据埋点之前要解决的问题</h3><p>看起来创建埋点数据是件很容易的事儿，但是想让这套方案在微服务集群环境下生效，我们还需要先解决两个核心问题：</p>
<ul>
<li><strong>Log系统集成</strong>：如何让埋点信息加入到业务Log中？</li>
<li><strong>埋点信息的传递：</strong>我们知道SpringCloud中的调用都是通过HTTP请求来传递的，那么上游调用方是如何将链路ID等信息传入到下游的呢？</li>
</ul>
<h3 id="Log-系统集成"><a href="#Log-系统集成" class="headerlink" title="Log 系统集成"></a>Log 系统集成</h3><p>我们需要把链路追踪信息加入到业务Log中，这些业务Log是我们研发人员写在具体服务里的，而不是Sleuth单独打印的log，因此Sleuth需要找到一个合适的切入点，让底层Log组件可以获取链路信息，并且我们的业务代码还不需要做任何改动。</p>
<p>如果有对Log框架做过深度定制的同学可能一下就能想到实现方式，就是使用MDC + Format Pattern的方式输出信息，我们先来看一下Log组件打印信息到文件的过程：</p>
<p><img src="https://gitee.com/littlefxc/oss/raw/master/images/5e13fe560945406628781494.png"></p>
<p>当我们使用<code>log.info()</code>打印日志的时候，Log组件会将“写入”动作封装成一个<code>LogEvent</code>事件，而这个事件的具体表现形式由 Log Format 和 MDC 共同控制，Format决定了Log的输出格式，而MDC决定了输出什么内容。</p>
<h3 id="Log-Format-Pattern"><a href="#Log-Format-Pattern" class="headerlink" title="Log Format Pattern"></a>Log Format Pattern</h3><p>Log组件定义了日志输出格式，这和我们平时使用“String.format”的方式差不多，集成了Sleuth后的Log输出格式是下面这个样子：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">&quot;%5p [sleuth-traceA,%X&#123;X-B3-TraceId:-&#125;,%X&#123;X-B3-SpanId:-&#125;,%X&#123;X-Span-Export:-&#125;]&quot;</span></span><br></pre></td></tr></table></figure>

<p>我们可以发现上面有几个<code>X</code>开头的占位符，这就是我们需要写入Log的链路追踪信息了。至于这几个符号分别对应链路信息的哪部分，下文会详细介绍。</p>
<h3 id="MDC"><a href="#MDC" class="headerlink" title="MDC"></a>MDC</h3><p>MDC是通过<code>InheritableThreadLocal</code>来实现的，它可以携带当前线程的上下文信息。它的底层是一个Map结构，存储了一系列Key-Value的值。Sleuth就是借助Spring的AOP机制，在方法调用的时候配置了切面，将链路追踪数据加入到了MDC中，这样在打印Log的时候，就能从MDC中获取这些值，填入到Log Format中的占位符里。</p>
<p>由于MDC基于InheritableThreadLocal而不是ThreadLocal实现，因此假如在当前线程中又开启了新的子线程，那么子线程依然会保留父线程的上下文信息。</p>
<p>源代码可以参考logback组件中<code>LogEvent</code>类的<code>prepareForDeferredProcessing</code>方法，了解MDC和Log Format是如何工作的。建议在打印log的地方打一个断点，本地启动项目后发起一次调用，然后一路跟进去一看便知。</p>
<h3 id="调用链路数据模型-Trace，Span，Annotation"><a href="#调用链路数据模型-Trace，Span，Annotation" class="headerlink" title="调用链路数据模型 Trace，Span，Annotation"></a>调用链路数据模型 Trace，Span，Annotation</h3><ul>
<li><strong>Trace</strong>：它就是从头到尾贯穿整个调用链的ID，我们叫它 Trace ID，不管调用链路中途访问了多少个服务节点，在每个节点的 log 中都会打印同一个 Trace ID。</li>
<li><strong>Span</strong>：它标识了 Sleuth 下面一个基本工作单元，每个单元都有一个独一无二的ID。比如服务A发起对服务B的调用，这个事件就可以看作一个独立单元，它生成了一个独立的ID。</li>
</ul>
<p>Span 不单单只是一个ID，它还包含一些其他信息，比如事件戳，它标识了一个事件从开始到结束的时间，我们可以用这个信息来统计接口的执行时间。每个 Span 还有一系列特殊的“标记”，也就是接下来要介绍的 “Annotation”，它标识了这个 Span 在执行过程中发起的一些特殊事件。</p>
<h2 id="Zipkin"><a href="#Zipkin" class="headerlink" title="Zipkin"></a>Zipkin</h2>
      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
    <link itemprop="mainEntityOfPage" href="http://littlefxc.github.io/2022/04/20/docs/%E5%BE%AE%E6%9C%8D%E5%8A%A1/Gateway/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="一年春又来">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="一年春又来">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/2022/04/20/docs/%E5%BE%AE%E6%9C%8D%E5%8A%A1/Gateway/" class="post-title-link" itemprop="url">Gateway</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">发表于</span>

      <time title="创建时间：2022-04-20 07:31:01" itemprop="dateCreated datePublished" datetime="2022-04-20T07:31:01+08:00">2022-04-20</time>
    </span>
      <span class="post-meta-item">
        <span class="post-meta-item-icon">
          <i class="far fa-calendar-check"></i>
        </span>
        <span class="post-meta-item-text">更新于</span>
        <time title="修改时间：2022-05-10 09:35:05" itemprop="dateModified" datetime="2022-05-10T09:35:05+08:00">2022-05-10</time>
      </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">分类于</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F/" itemprop="url" rel="index"><span itemprop="name">分布式系统</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <h2 id="1-Spring-Cloud-Gateway-能做什么？"><a href="#1-Spring-Cloud-Gateway-能做什么？" class="headerlink" title="1 Spring Cloud Gateway 能做什么？"></a>1 Spring Cloud Gateway 能做什么？</h2><p><img src="https://img-blog.csdnimg.cn/img_convert/45680ad1cd1751edfb2ccbbe1c2eb829.png" alt="image-20220420203439069"></p>
<ol>
<li>路由寻址（主要功能）</li>
<li>负载均衡</li>
<li>限流</li>
<li>鉴权</li>
</ol>
          <!--noindex-->
            <div class="post-button">
              <a class="btn" href="/2022/04/20/docs/%E5%BE%AE%E6%9C%8D%E5%8A%A1/Gateway/#more" rel="contents">
                阅读全文 &raquo;
              </a>
            </div>
          <!--/noindex-->
        
      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
    <link itemprop="mainEntityOfPage" href="http://littlefxc.github.io/2022/04/15/docs/MySQL/MySQL%E4%BA%8B%E5%8A%A1%E7%9A%844%E7%A7%8D%E9%9A%94%E7%A6%BB%E7%BA%A7%E5%88%AB/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="一年春又来">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="一年春又来">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/2022/04/15/docs/MySQL/MySQL%E4%BA%8B%E5%8A%A1%E7%9A%844%E7%A7%8D%E9%9A%94%E7%A6%BB%E7%BA%A7%E5%88%AB/" class="post-title-link" itemprop="url">MySQL事务的4种隔离级别</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">发表于</span>

      <time title="创建时间：2022-04-15 07:47:13" itemprop="dateCreated datePublished" datetime="2022-04-15T07:47:13+08:00">2022-04-15</time>
    </span>
      <span class="post-meta-item">
        <span class="post-meta-item-icon">
          <i class="far fa-calendar-check"></i>
        </span>
        <span class="post-meta-item-text">更新于</span>
        <time title="修改时间：2022-05-10 09:23:06" itemprop="dateModified" datetime="2022-05-10T09:23:06+08:00">2022-05-10</time>
      </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">分类于</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/" itemprop="url" rel="index"><span itemprop="name">数据库</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <h1 id="MySQL事务的4种隔离级别"><a href="#MySQL事务的4种隔离级别" class="headerlink" title="MySQL事务的4种隔离级别"></a>MySQL事务的4种隔离级别</h1><h1 id="1-简介"><a href="#1-简介" class="headerlink" title="1 简介"></a>1 简介</h1><p>事务的4种隔离级别分别是读未提交（Read Uncommitted）、读已提交（Read Committed）、 可重复读（Repeatable Read）和串行化（Serializable）。</p>
<p>首先，在了解这4种隔离级别前就必须先要了解其前提，也就是事务，本文简单介绍一下关于事务。</p>
<p>之后，我们也要理解这4种隔离级别产生的原因和场景展现以及4种隔离级别是如何解决问题的。</p>
<h1 id="2-什么是数据库事务？"><a href="#2-什么是数据库事务？" class="headerlink" title="2 什么是数据库事务？"></a>2 什么是数据库事务？</h1><p>事务由一个有限的数据库操作序列组成，这些操作要么全部执行，要么全部不执行，是一个不可分割的工作单位。</p>
<p>例如一个银行转账场景：</p>
<p>A转账B 100元，A的账号扣除100元，B的账号加上100块。假如中间出现任何异常，例如，在A的账号扣100元时，银行瘫痪，B的账号余额没有发生变化。这时候就需要事务来保证将A的钱还回去。</p>
<h2 id="2-1-事务的四大特性（ACID）"><a href="#2-1-事务的四大特性（ACID）" class="headerlink" title="2.1 事务的四大特性（ACID）"></a>2.1 事务的四大特性（ACID）</h2><ul>
<li>原子性：事务作为一个整体被执行，包含在其中的对数据库的操作要么全部都执行，要么都不执行。</li>
<li>一致性：指在事务开始之前和事务结束以后，数据不会被破坏，假如A账户给B账户转10块钱，不管成功与否，A和B的总金额是不变的。</li>
<li>隔离性：多个事务并发访问时，事务之间是相互隔离的，一个事务不应该被其他事务干扰，多个并发事务之间要相互隔离。</li>
<li>持久性：表示事务完成提交后，该事务对数据库所作的操作更改，将持久地保存在数据库之中。</li>
</ul>
<h1 id="3-并发事务会导致的问题"><a href="#3-并发事务会导致的问题" class="headerlink" title="3 并发事务会导致的问题"></a>3 并发事务会导致的问题</h1><ul>
<li>脏读：事务 A 读取了事务 B 更新的数据，然后 B 进行回滚操作，那么A读取的数据就是脏数据</li>
<li>不可重复读：事务A多次读取同一数据，事务B在事务A多次读取的过程中，对数据做了更新并提交，导致事务A多次夺取同一数据时，结果不一致。</li>
<li>幻读：系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级，但是系统管理员B就在这个时候插入了一条具体分数的记录，当系统管理员A改结束后发现还有一条记录没有改过来，就好像发生了幻觉一样，这就叫幻读。</li>
</ul>
<blockquote>
<p>💡 不可重复读的和幻读很容易混淆，不可重复读侧重于修改，幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行，解决幻读需要锁表。</p>
</blockquote>
<h2 id="3-1-本文会使用到的-SQL-语句："><a href="#3-1-本文会使用到的-SQL-语句：" class="headerlink" title="3.1 本文会使用到的 SQL 语句："></a>3.1 本文会使用到的 SQL 语句：</h2><h3 id="3-1-1-示例表结构"><a href="#3-1-1-示例表结构" class="headerlink" title="3.1.1 示例表结构"></a>3.1.1 示例表结构</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> <span class="keyword">TABLE</span> `account` (</span><br><span class="line">  `id` <span class="type">int</span>(<span class="number">11</span>) <span class="keyword">NOT</span> <span class="keyword">NULL</span>,</span><br><span class="line">  `name` <span class="type">varchar</span>(<span class="number">255</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span>,</span><br><span class="line">  `balance` <span class="type">int</span>(<span class="number">11</span>) <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span>,</span><br><span class="line">  <span class="keyword">PRIMARY</span> KEY (`id`),</span><br><span class="line">  <span class="keyword">UNIQUE</span> KEY `un_name_idx` (`name`) <span class="keyword">USING</span> BTREE</span><br><span class="line">) ENGINE<span class="operator">=</span>InnoDB <span class="keyword">DEFAULT</span> CHARSET<span class="operator">=</span>utf8;</span><br></pre></td></tr></table></figure>

<h3 id="3-1-2-查询事务的默认隔离级别"><a href="#3-1-2-查询事务的默认隔离级别" class="headerlink" title="3.1.2 查询事务的默认隔离级别"></a>3.1.2 查询事务的默认隔离级别</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">mysql<span class="operator">&gt;</span> <span class="keyword">select</span> @<span class="variable">@transaction</span>_isolation;</span><br><span class="line"><span class="operator">+</span><span class="comment">-------------------------+</span></span><br><span class="line"><span class="operator">|</span> @<span class="variable">@transaction</span>_isolation <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">-------------------------+</span></span><br><span class="line"><span class="operator">|</span> REPEATABLE<span class="operator">-</span>READ         <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">-------------------------+</span></span><br><span class="line"><span class="number">1</span> <span class="type">row</span> <span class="keyword">in</span> <span class="keyword">set</span> (<span class="number">0.01</span> sec)</span><br></pre></td></tr></table></figure>

<h3 id="3-1-3-设置当前会话的事务隔离级别"><a href="#3-1-3-设置当前会话的事务隔离级别" class="headerlink" title="3.1.3 设置当前会话的事务隔离级别"></a>3.1.3 设置当前会话的事务隔离级别</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">mysql<span class="operator">&gt;</span> <span class="keyword">set</span> session transaction isolation level read uncommitted;</span><br><span class="line">Query OK, <span class="number">0</span> <span class="keyword">rows</span> affected (<span class="number">0.00</span> sec)</span><br></pre></td></tr></table></figure>

<h1 id="4-事务的4种隔离级别和示例演示"><a href="#4-事务的4种隔离级别和示例演示" class="headerlink" title="4 事务的4种隔离级别和示例演示"></a>4 事务的4种隔离级别和示例演示</h1><table>
<thead>
<tr>
<th>事务隔离级别</th>
<th>脏读</th>
<th>不可重复读</th>
<th>幻读</th>
</tr>
</thead>
<tbody><tr>
<td>读未提交（read-uncommitted）</td>
<td>是</td>
<td>是</td>
<td>是</td>
</tr>
<tr>
<td>不可重复读（read-committed）又叫读已提交</td>
<td>否</td>
<td>是</td>
<td>是</td>
</tr>
<tr>
<td>可重复读（repeatable-read）</td>
<td>否</td>
<td>否</td>
<td>是</td>
</tr>
<tr>
<td>串行化（serializable）</td>
<td>否</td>
<td>否</td>
<td>否</td>
</tr>
</tbody></table>
<h2 id="4-1-读未提交"><a href="#4-1-读未提交" class="headerlink" title="4.1 读未提交"></a>4.1 读未提交</h2><p>事务A：</p>
<p><img src="https://gitee.com/littlefxc/oss/raw/master/images/mysql_transaction_0.png" alt="Untitled"></p>
<p>事务B：</p>
<p><img src="https://gitee.com/littlefxc/oss/raw/master/images/mysql_transaction_1.png" alt="Untitled"></p>
<blockquote>
<p>💡 读未提交是隔离级别最低的，会造成脏读。</p>
</blockquote>
<h2 id="4-2-读已提交"><a href="#4-2-读已提交" class="headerlink" title="4.2 读已提交"></a>4.2 读已提交</h2><p>为了避免脏读，数据库有了比<strong>读未提交</strong>更高的隔离级别，即<strong>读已提交</strong>。</p>
<p>对于提交：当前事务只能读取其它事务已提交的数据，未提交事务的数据读取不到。</p>
<p>事务A：</p>
<p><img src="https://gitee.com/littlefxc/oss/raw/master/images/mysql_transaction_2.png" alt="Untitled"></p>
<p>事务B：</p>
<p><img src="https://gitee.com/littlefxc/oss/raw/master/images/mysql_transaction_3.png" alt="Untitled"></p>
<p>由此可以得出结论，隔离级别设置为<strong>已提交读（READ COMMITTED）</strong><br> 时，已经不会出现脏读问题了，当前事务只能读取到其他事务提交的数据。但是，站在事务A的角度想想，存在其他问题吗？</p>
<h3 id="提交读的隔离级别会有什么问题呢？"><a href="#提交读的隔离级别会有什么问题呢？" class="headerlink" title="提交读的隔离级别会有什么问题呢？"></a><strong>提交读的隔离级别会有什么问题呢？</strong></h3><p>在同一个事务A里，相同的查询sql，读取同一条记录（id=1），读到的结果是不一样的，即<strong>不可重复读</strong>。所以，隔离级别设置为<code>read committed</code>的时候，还会存在<strong>不可重复读</strong>的并发问题。</p>
<h2 id="4-3-可重复读"><a href="#4-3-可重复读" class="headerlink" title="4.3 可重复读"></a>4.3 可重复读</h2><p>为了避免<strong>不可重复读</strong>的并发问题，我们将隔离级别设置为<strong>可重复读（REPEATABLEE READ）</strong>，重复一下之前的操作。</p>
<p>事务A：</p>
<p><img src="https://gitee.com/littlefxc/oss/raw/master/images/mysql_transaction_4-20220415155652729.png" alt="Untitled"></p>
<p>事务B：</p>
<p><img src="https://gitee.com/littlefxc/oss/raw/master/images/mysql_transaction_5.png" alt="Untitled"></p>
<p><a target="_blank" rel="noopener" href="https://gitee.com/littlefxc/oss/raw/master/images/mysql_transaction_5.png">https://gitee.com/littlefxc/oss/raw/master/images/mysql_transaction_5.png</a></p>
<p>到了这一步，可以发现事务隔离级别设置为可重复读，可以解决幻读问题。</p>
<h3 id="那么可重复读真的是否已经解决了幻读问题呢？毕竟还剩个事务隔离级别呢。"><a href="#那么可重复读真的是否已经解决了幻读问题呢？毕竟还剩个事务隔离级别呢。" class="headerlink" title="那么可重复读真的是否已经解决了幻读问题呢？毕竟还剩个事务隔离级别呢。"></a>那么<strong>可重复读</strong>真的是否已经解决了幻读问题呢？毕竟还剩个事务隔离级别呢。</h3><p>RR隔离级别下，手动启动一个事务，进行select操作，他会生成一个快照，可以理解为将当前数据库的数据复制一份，在当前事务中，之后不管进行多少次select查询，都是在模板中去取数据，所以不管数据库中是否对数据进行了改变，都不会影响当前事务数据的读取，从而避免了幻读。<strong>这种普通的 select 操作，称为快照读</strong>。</p>
<p>但是如果在当前事务中使用了下图语句进行<strong>当前读</strong>:</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> account <span class="keyword">for</span> update;</span><br></pre></td></tr></table></figure>

<p><code>for update</code>是进行<strong>当前读</strong>的操作，他会重新从数据库去加载当前的最新的数据，每执行一次加载一次，如果在此时，另外一个事务为数据库添加了一个事务，再进行查询，会发现查询的数据与之前相比多了或者少了，这也就是幻读现象。</p>
<p>如果你阅读到这里，去实操一下，会发现和我说的不一样，有一种上当的感觉。</p>
<p>其实不是的，这是因为上述都是在标准的可重复读下的情况，在innodb存储引擎中对可重复读进行了改造，为当前读加上了 <code>Next-key Lock</code>,也就是间隙锁和行锁的统称，<strong>行锁防止了别的事务修改或者删除，间隙锁防止了别的事务新增</strong>。也就是在进行上面的<code>for update</code>事务中，其他的事务不能对数据进行增删操作，执行会报错或者长时间处于等待状态。</p>
<blockquote>
<p>💡 注意：如果A事务如果进行了快照读，然后通过B事务对数据就行增删，然后紧接着A事务进行当前读操作，两次读取数据不一致，不能算作幻读，因为幻读定义是同一个select语句，快照读和当前读的查询语句是不一样的.</p>
</blockquote>
<h3 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h3><ol>
<li><strong>数据库的并发问题有：脏读、不可重复读和幻读；</strong></li>
<li><strong>事务隔离级别依次为：读未提交、读已提交、可重复读和串行化；</strong></li>
<li><strong>在标准的RR下并没有彻底解决幻读，但是在Mysql的innodb引擎中彻底解决了；</strong></li>
<li><strong>innodb通过</strong> Next-Key lock<strong>解决的幻读问题，其实也就是阻塞串行化了；</strong></li>
<li><strong>不能把快照读和当前读在一个事务中进行比较是否出现幻读，两者不是同一个select，不满足幻读的官方定义。</strong></li>
</ol>
<h2 id="4-4-串行化"><a href="#4-4-串行化" class="headerlink" title="4.4 串行化"></a>4.4 串行化</h2><p>略，这部分我懒得放图了，因为结果和上面没啥差别。</p>
<h1 id="相关性"><a href="#相关性" class="headerlink" title="相关性"></a>相关性</h1><hr>
<h1 id="文献引用"><a href="#文献引用" class="headerlink" title="文献引用"></a>文献引用</h1><hr>
<p><a target="_blank" rel="noopener" href="https://www.cnblogs.com/jay-huaxiao/p/12639435.html">一文彻底读懂MySQL事务的四大隔离级别 - Jay_huaxiao - 博客园</a></p>
<p><a target="_blank" rel="noopener" href="https://www.cnblogs.com/yaochunhui/p/14177906.html">MySQL的四种事务隔离级别</a></p>
<p><a target="_blank" rel="noopener" href="https://blog.csdn.net/qq_45225798/article/details/119978841">关于数据库隔离级别为RR(可重复读)下是否解决幻读问题_眉梢i的博客-CSDN博客_rr解决幻读</a></p>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
    <link itemprop="mainEntityOfPage" href="http://littlefxc.github.io/2022/04/07/docs/%E5%BE%AE%E6%9C%8D%E5%8A%A1/RocketMQ%E4%BA%8B%E5%8A%A1%E6%B6%88%E6%81%AF%E7%A4%BA%E4%BE%8B/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="一年春又来">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="一年春又来">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/2022/04/07/docs/%E5%BE%AE%E6%9C%8D%E5%8A%A1/RocketMQ%E4%BA%8B%E5%8A%A1%E6%B6%88%E6%81%AF%E7%A4%BA%E4%BE%8B/" class="post-title-link" itemprop="url">RocketMQ事务消息示例</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">发表于</span>

      <time title="创建时间：2022-04-07 09:14:16" itemprop="dateCreated datePublished" datetime="2022-04-07T09:14:16+08:00">2022-04-07</time>
    </span>
      <span class="post-meta-item">
        <span class="post-meta-item-icon">
          <i class="far fa-calendar-check"></i>
        </span>
        <span class="post-meta-item-text">更新于</span>
        <time title="修改时间：2022-05-10 09:28:36" itemprop="dateModified" datetime="2022-05-10T09:28:36+08:00">2022-05-10</time>
      </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">分类于</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F/" itemprop="url" rel="index"><span itemprop="name">分布式系统</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>分布式事务是一个复杂的问题，本文就基于 RocketMQ 来实现最终一种性方案的分布式事务的示例与测试。</p>
<h2 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h2><p><img src="https://gitee.com/littlefxc/oss/raw/master/images/RocketMQ%E4%BA%8B%E5%8A%A1%E6%B6%88%E6%81%AF%E7%A4%BA%E4%BE%8B.jpg" alt="RocketMQ事务消息示例"></p>
<p>整体的流程如上所示。</p>
<p>RocketMQ 事务消息的原理是基于两阶段提交和事务状态回查。</p>
<ul>
<li><p>半消息：是指暂时不能被消费的消息，半消息实际上被放在主题名为 <code>RMQ_SYS_TRANS_HALF_TOPIC</code>下，当 producer 对半消息进行二次确认后，也就是上图的第 4 步后，consumer 才可以消费。</p>
</li>
<li><p>事务状态回查：如果上图的第 4 步，半消息提交因为种种原因（网络原因、producer崩溃）失败了，而导致 broker 不能收到 producer 的确认消息，那么 broker 就会定时扫描这些半消息，主动去确认。</p>
<p>当然，这个定时机制也是可以配置的。</p>
</li>
</ul>
<p>最重要的两个概念就介绍到这里啦，其它的就不啰嗦了。</p>
<p>业务流程：每增加一个订单，就增加相应的积分。</p>
<h2 id="数据库"><a href="#数据库" class="headerlink" title="数据库"></a>数据库</h2><p>数据库有两个，一个包含订单表和事务日志表，另一个则只有订单积分表。</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 本地业务</span></span><br><span class="line"><span class="keyword">CREATE</span> <span class="keyword">TABLE</span> `orders` (</span><br><span class="line">  `id` <span class="type">int</span> unsigned <span class="keyword">NOT</span> <span class="keyword">NULL</span> AUTO_INCREMENT,</span><br><span class="line">  `user_id` <span class="type">int</span> unsigned <span class="keyword">NOT</span> <span class="keyword">NULL</span>,</span><br><span class="line">  `goods_name` <span class="type">varchar</span>(<span class="number">255</span>) <span class="keyword">NOT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;商品名&#x27;</span>,</span><br><span class="line">  `total` <span class="type">int</span> unsigned <span class="keyword">NOT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;数量&#x27;</span>,</span><br><span class="line">  <span class="keyword">PRIMARY</span> KEY (`id`)</span><br><span class="line">) ENGINE<span class="operator">=</span>InnoDB AUTO_INCREMENT<span class="operator">=</span><span class="number">9</span> <span class="keyword">DEFAULT</span> CHARSET<span class="operator">=</span>utf8;</span><br><span class="line"></span><br><span class="line"><span class="comment">-- 这张表专门用于事务状态回查</span></span><br><span class="line"><span class="comment">-- 当本地业务提交后，此表也插入一条记录，两者处于同一个事务中</span></span><br><span class="line"><span class="comment">-- 通过 RocketMQ事务ID 查询该表，如果返回记录，则证明本地事务已提交；如果未返回记录，则本地事务可能是未知状态或者是回滚状态。</span></span><br><span class="line"><span class="keyword">CREATE</span> <span class="keyword">TABLE</span> `transaction_log` (</span><br><span class="line">  `id` <span class="type">varchar</span>(<span class="number">32</span>) <span class="keyword">NOT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;事务ID&#x27;</span>,</span><br><span class="line">  `business` <span class="type">varchar</span>(<span class="number">32</span>) <span class="keyword">NOT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;业务标识&#x27;</span>,</span><br><span class="line">  `foreign_key` <span class="type">varchar</span>(<span class="number">32</span>) <span class="keyword">NOT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;对应业务表中的主键&#x27;</span>,</span><br><span class="line">  <span class="keyword">PRIMARY</span> KEY (`id`)</span><br><span class="line">) ENGINE<span class="operator">=</span>InnoDB <span class="keyword">DEFAULT</span> CHARSET<span class="operator">=</span>utf8mb4 <span class="keyword">COLLATE</span><span class="operator">=</span>utf8mb4_0900_ai_ci;</span><br></pre></td></tr></table></figure>

<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">-- 远端业务</span></span><br><span class="line"><span class="keyword">CREATE</span> <span class="keyword">TABLE</span> `order_credits` (</span><br><span class="line">  `id` <span class="type">int</span> unsigned <span class="keyword">NOT</span> <span class="keyword">NULL</span> AUTO_INCREMENT,</span><br><span class="line">  `user_id` <span class="type">int</span> unsigned <span class="keyword">NOT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;用户ID&#x27;</span>,</span><br><span class="line">  `order_id` <span class="type">int</span> unsigned <span class="keyword">NOT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;订单ID&#x27;</span>,</span><br><span class="line">  `total` <span class="type">int</span> unsigned <span class="keyword">NOT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;积分数量&#x27;</span>,</span><br><span class="line">  <span class="keyword">PRIMARY</span> KEY (`id`)</span><br><span class="line">) ENGINE<span class="operator">=</span>InnoDB AUTO_INCREMENT<span class="operator">=</span><span class="number">9</span> <span class="keyword">DEFAULT</span> CHARSET<span class="operator">=</span>utf8;</span><br></pre></td></tr></table></figure>

<h2 id="核心代码"><a href="#核心代码" class="headerlink" title="核心代码"></a>核心代码</h2><h3 id="事务管理工具类"><a href="#事务管理工具类" class="headerlink" title="事务管理工具类"></a>事务管理工具类</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.fengxuechao.example.rocketmq;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.sql.*;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 事务管理工具类</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> fengxuechao</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@date</span> 2022/4/7</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TransactionUtil</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> ThreadLocal&lt;Connection&gt; connections = <span class="keyword">new</span> ThreadLocal&lt;&gt;();</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="title">TransactionUtil</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 开启事务, jdbcUrl 要记得修改</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Connection <span class="title">startTransaction</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        Connection connection = connections.get();</span><br><span class="line">        <span class="keyword">if</span> (connection == <span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                connection = DriverManager.getConnection(</span><br><span class="line">                        <span class="string">&quot;jdbc:mysql://localhost:3306/rocketmq?serverTimezone=GMT%2B8&quot;</span>,</span><br><span class="line">                        <span class="string">&quot;root&quot;</span>,</span><br><span class="line">                        <span class="string">&quot;12345678&quot;</span>);</span><br><span class="line">                connection.setAutoCommit(<span class="keyword">false</span>);</span><br><span class="line">                connections.set(connection);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (SQLException e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> connection;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">int</span> <span class="title">execute</span><span class="params">(String sql, Object... args)</span> <span class="keyword">throws</span> SQLException </span>&#123;</span><br><span class="line">        PreparedStatement preparedStatement = createPreparedStatement(sql, args);</span><br><span class="line">        <span class="keyword">return</span> preparedStatement.executeUpdate();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> ResultSet <span class="title">select</span><span class="params">(String sql, Object... args)</span> <span class="keyword">throws</span> SQLException </span>&#123;</span><br><span class="line">        PreparedStatement preparedStatement = createPreparedStatement(sql, args);</span><br><span class="line">        preparedStatement.execute();</span><br><span class="line">        <span class="keyword">return</span> preparedStatement.getResultSet();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> PreparedStatement <span class="title">createPreparedStatement</span><span class="params">(String sql, Object[] args)</span> <span class="keyword">throws</span> SQLException </span>&#123;</span><br><span class="line">        Connection connection = startTransaction();</span><br><span class="line">        PreparedStatement preparedStatement = connection.prepareStatement(sql);</span><br><span class="line">        <span class="keyword">if</span> (args != <span class="keyword">null</span>) &#123;</span><br><span class="line">            <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; args.length; i++) &#123;</span><br><span class="line">                preparedStatement.setObject(i + <span class="number">1</span>, args[i]);</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> preparedStatement;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 提交事务</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">commit</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">try</span> (Connection connection = connections.get()) &#123;</span><br><span class="line">            connection.commit();</span><br><span class="line">            connections.remove();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (SQLException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 回滚事务</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">rollback</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">try</span> (Connection connection = connections.get()) &#123;</span><br><span class="line">            connection.rollback();</span><br><span class="line">            connections.remove();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (SQLException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="生产者业务代码"><a href="#生产者业务代码" class="headerlink" title="生产者业务代码"></a>生产者业务代码</h3><blockquote>
<p>发送半消息。对应的是上图中的第 1 步。</p>
<p>注意点：如果发送事务消息，在这里我们的创建的实例必须是 <code>TransactionMQProducer</code>。</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.fengxuechao.example.rocketmq;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.alibaba.fastjson.JSON;</span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.client.exception.MQClientException;</span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.client.producer.TransactionListener;</span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.client.producer.TransactionMQProducer;</span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.common.message.Message;</span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.remoting.common.RemotingHelper;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.io.UnsupportedEncodingException;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.ArrayBlockingQueue;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.ExecutorService;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.ThreadPoolExecutor;</span><br><span class="line"><span class="keyword">import</span> java.util.concurrent.TimeUnit;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 消息事务生产者</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> fengxuechao</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@date</span> 2022/4/6</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TransactionProducer</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> MQClientException, InterruptedException </span>&#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 生产者事务监听器</span></span><br><span class="line">        TransactionListener transactionListener = <span class="keyword">new</span> OrderTransactionListener();</span><br><span class="line">        TransactionMQProducer producer = <span class="keyword">new</span> TransactionMQProducer();</span><br><span class="line">        ExecutorService executorService = <span class="keyword">new</span> ThreadPoolExecutor(</span><br><span class="line">                <span class="number">2</span>, <span class="number">5</span>, <span class="number">100</span>,</span><br><span class="line">                TimeUnit.SECONDS, <span class="keyword">new</span> ArrayBlockingQueue&lt;Runnable&gt;(<span class="number">2000</span>),</span><br><span class="line">                r -&gt; <span class="keyword">new</span> Thread(r, <span class="string">&quot;client-transaction-msg-check-thread&quot;</span>));</span><br><span class="line"></span><br><span class="line">        producer.setExecutorService(executorService);</span><br><span class="line">        producer.setTransactionListener(transactionListener);</span><br><span class="line">        producer.setNamesrvAddr(<span class="string">&quot;127.0.0.1:9876&quot;</span>);</span><br><span class="line">        producer.setProducerGroup(<span class="string">&quot;producer_order_trans_group&quot;</span>);</span><br><span class="line">        producer.start();</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 发送消息</span></span><br><span class="line">        String topic = <span class="string">&quot;transaction-topic&quot;</span>;</span><br><span class="line">        String tags = <span class="string">&quot;trans-order&quot;</span>;</span><br><span class="line">        Order order = <span class="keyword">new</span> Order();</span><br><span class="line">        order.setId(<span class="number">1</span>);</span><br><span class="line">        order.setUserId(<span class="number">1</span>);</span><br><span class="line">        order.setGoodsName(<span class="string">&quot;小脆面&quot;</span>);</span><br><span class="line">        order.setTotal(<span class="number">2</span>);</span><br><span class="line">        String orderJson = JSON.toJSONString(order);</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">byte</span>[] orderBytes = orderJson.getBytes(RemotingHelper.DEFAULT_CHARSET);</span><br><span class="line">            Message msg = <span class="keyword">new</span> Message(topic, tags, <span class="string">&quot;order&quot;</span>, orderBytes);</span><br><span class="line">            producer.sendMessageInTransaction(msg, <span class="keyword">null</span>);</span><br><span class="line">        &#125; <span class="keyword">catch</span> (UnsupportedEncodingException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        producer.shutdown();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<blockquote>
<ul>
<li><p>半消息确认，执行本地事务。对应的是<code>executeLocalTransaction</code>这个方法，需要注意的是本地业务提交后，事务日志表也插入一条记录，两者处于同一个事务中。</p>
</li>
<li><p>回查事务状态。对应的是<code>checkLocalTransaction</code>这个方法。</p>
<ul>
<li>在这里，我们通过事务ID查询<code>transaction_log</code>这张表，如果可以查询到结果，就提交事务消息；如果没有查询到，就返回未知状态。</li>
<li>如果返回未知状态，broker 会以1分钟的间隔时间不断回查，直至达到事务回查最大检测数，如果超过这个数字还未查询到事务状态，则回滚此消息。</li>
</ul>
</li>
</ul>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.fengxuechao.example.rocketmq;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.alibaba.fastjson.JSON;</span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.client.producer.LocalTransactionState;</span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.client.producer.TransactionListener;</span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.common.message.Message;</span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.common.message.MessageExt;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.sql.ResultSet;</span><br><span class="line"><span class="keyword">import</span> java.sql.SQLException;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> fengxuechao</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@date</span> 2022/4/7</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">OrderTransactionListener</span> <span class="keyword">implements</span> <span class="title">TransactionListener</span> </span>&#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * When send transactional prepare(half) message succeed, this method will be invoked to execute local transaction.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> msg Half(prepare) message</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> arg Custom business parameter</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> Transaction state</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> LocalTransactionState <span class="title">executeLocalTransaction</span><span class="params">(Message msg, Object arg)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// RocketMQ 半消息发送成功，开始执行本地事务</span></span><br><span class="line">        System.out.println(<span class="string">&quot;执行本地事务&quot;</span>);</span><br><span class="line">        TransactionUtil.startTransaction();</span><br><span class="line">        LocalTransactionState state;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// 创建订单</span></span><br><span class="line">            System.out.println(<span class="string">&quot;创建订单&quot;</span>);</span><br><span class="line">            String orderStr = <span class="keyword">new</span> String(msg.getBody());</span><br><span class="line">            Order order = JSON.parseObject(orderStr, Order.class);</span><br><span class="line">            String sql = <span class="string">&quot;insert into orders(id, user_id, goods_name, total) values(?, ?, ?, ?)&quot;</span>;</span><br><span class="line">            <span class="keyword">int</span> executeUpdates = TransactionUtil.execute(sql, order.getId(), order.getUserId(),</span><br><span class="line">                    order.getGoodsName(), order.getTotal());</span><br><span class="line">            <span class="keyword">if</span> (executeUpdates &gt; <span class="number">0</span>) &#123;</span><br><span class="line">                <span class="comment">// 写入本地事务日志</span></span><br><span class="line">                System.out.println(<span class="string">&quot;写入本地事务日志&quot;</span>);</span><br><span class="line">                String logSql = <span class="string">&quot;insert into transaction_log(id, business, foreign_key) values(?, ?, ?)&quot;</span>;</span><br><span class="line">                String business = msg.getKeys();</span><br><span class="line">                TransactionUtil.execute(logSql, msg.getTransactionId(), business, order.getId());</span><br><span class="line">            &#125;</span><br><span class="line">            TransactionUtil.commit();</span><br><span class="line">            state = LocalTransactionState.COMMIT_MESSAGE;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (SQLException e) &#123;</span><br><span class="line">            TransactionUtil.rollback();</span><br><span class="line">            state = LocalTransactionState.ROLLBACK_MESSAGE;</span><br><span class="line">            System.out.println(<span class="string">&quot;本地事务异常，回滚&quot;</span>);</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> state;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * When no response to prepare(half) message. broker will send check message to check the transaction status, and this</span></span><br><span class="line"><span class="comment">     * method will be invoked to get local transaction status.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> msg Check message</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span> Transaction state</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> LocalTransactionState <span class="title">checkLocalTransaction</span><span class="params">(MessageExt msg)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 回查本地事务</span></span><br><span class="line">        System.out.printf(<span class="string">&quot;回查本地事务, transactionId = %s%n&quot;</span>, msg.getTransactionId());</span><br><span class="line">        TransactionUtil.startTransaction();</span><br><span class="line">        String sql = <span class="string">&quot;select id, business, foreign_key from transaction_log where id = ?&quot;</span>;</span><br><span class="line">        <span class="keyword">try</span> (ResultSet transactionLog = TransactionUtil.select(sql, msg.getTransactionId())) &#123;</span><br><span class="line">            <span class="keyword">if</span> (transactionLog == <span class="keyword">null</span>) &#123;</span><br><span class="line">                <span class="keyword">return</span> LocalTransactionState.UNKNOW;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (SQLException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> LocalTransactionState.COMMIT_MESSAGE;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h3 id="消费者业务代码"><a href="#消费者业务代码" class="headerlink" title="消费者业务代码"></a>消费者业务代码</h3><blockquote>
<p>这里是增加积分的阶段。</p>
<ul>
<li><p>需要注意的是幂等性消费，总的思路就是在执行业务前，必需确认该消息是否被处理过。可以使用 RocketMQ 事务消息的 ID，也可以使用订单ID。</p>
</li>
<li><p>第二个需要注意的是消息一直不能成功消费。这个时候，我想到两种方式处理：</p>
<ul>
<li>在代码中设置消息重试次数，然后发送邮件或其他方式通知业务方人工处理</li>
<li>或者等待消息达到最大重试次数，进入死信队列（主题：%DLQ% + 消费者组名称）。</li>
</ul>
</li>
</ul>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> com.fengxuechao.example.rocketmq;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> com.alibaba.fastjson.JSON;</span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;</span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;</span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;</span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;</span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.client.exception.MQClientException;</span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.common.consumer.ConsumeFromWhere;</span><br><span class="line"><span class="keyword">import</span> org.apache.rocketmq.common.message.MessageExt;</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> java.nio.charset.StandardCharsets;</span><br><span class="line"><span class="keyword">import</span> java.sql.ResultSet;</span><br><span class="line"><span class="keyword">import</span> java.util.List;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> fengxuechao</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@date</span> 2022/4/7</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TransactionConsumer</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> MQClientException </span>&#123;</span><br><span class="line">        DefaultMQPushConsumer consumer = <span class="keyword">new</span> DefaultMQPushConsumer(<span class="string">&quot;consumer_order_trans_group&quot;</span>);</span><br><span class="line">        consumer.setNamesrvAddr(<span class="string">&quot;127.0.0.1:9876&quot;</span>);</span><br><span class="line">        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);</span><br><span class="line">        consumer.subscribe(<span class="string">&quot;transaction-topic&quot;</span>, <span class="string">&quot;trans-order&quot;</span>);</span><br><span class="line">        consumer.setMaxReconsumeTimes(<span class="number">3</span>);</span><br><span class="line">        TransactionUtil.startTransaction();</span><br><span class="line">        consumer.registerMessageListener(<span class="keyword">new</span> MessageListenerConcurrently() &#123;</span><br><span class="line">            <span class="meta">@Override</span></span><br><span class="line">            <span class="function"><span class="keyword">public</span> ConsumeConcurrentlyStatus <span class="title">consumeMessage</span><span class="params">(List&lt;MessageExt&gt; msgs, ConsumeConcurrentlyContext context)</span> </span>&#123;</span><br><span class="line">                <span class="keyword">try</span> &#123;</span><br><span class="line">                    <span class="keyword">for</span> (MessageExt msg : msgs) &#123;</span><br><span class="line">                        <span class="comment">// 多次消费消息处理仍然失败后，发送邮件，人工处理</span></span><br><span class="line">                        <span class="keyword">if</span> (msg.getReconsumeTimes() &gt;= <span class="number">3</span>) &#123;</span><br><span class="line">                            <span class="comment">// 发送邮件，人工处理</span></span><br><span class="line">                            sendMail();</span><br><span class="line">                        &#125;</span><br><span class="line"></span><br><span class="line">                        String orderStr = <span class="keyword">new</span> String(msg.getBody(), StandardCharsets.UTF_8);</span><br><span class="line">                        Order order = JSON.parseObject(orderStr, Order.class);</span><br><span class="line">                        <span class="comment">// 幂等性保持</span></span><br><span class="line">                        String sql1 = <span class="string">&quot;select * from order_credits where order_id = ?&quot;</span>;</span><br><span class="line">                        ResultSet rs = TransactionUtil.select(sql1, order.getId());</span><br><span class="line">                        <span class="keyword">if</span> (rs != <span class="keyword">null</span> &amp;&amp; rs.next()) &#123;</span><br><span class="line">                            System.out.println(<span class="string">&quot;积分已添加，订单已处理！&quot;</span>);</span><br><span class="line">                        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                            <span class="comment">// 增加积分</span></span><br><span class="line">                            String sql2 = <span class="string">&quot;insert into order_credits(user_id,order_id,total) values(?,?,?)&quot;</span>;</span><br><span class="line">                            TransactionUtil.execute(sql2, order.getUserId(), order.getId(), order.getTotal() * <span class="number">2</span>);</span><br><span class="line">                            System.out.printf(<span class="string">&quot;订单（id=%s）添加积分%n&quot;</span>, order.getId());</span><br><span class="line">                            TransactionUtil.commit();</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;</span><br><span class="line">                    <span class="keyword">return</span> ConsumeConcurrentlyStatus.CONSUME_SUCCESS;</span><br><span class="line">                &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">                    TransactionUtil.rollback();</span><br><span class="line">                    e.printStackTrace();</span><br><span class="line">                &#125;</span><br><span class="line">                <span class="keyword">return</span> ConsumeConcurrentlyStatus.RECONSUME_LATER;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">sendMail</span><span class="params">()</span> </span>&#123; &#125;</span><br><span class="line">        &#125;);</span><br><span class="line"></span><br><span class="line">        consumer.start();</span><br><span class="line"></span><br><span class="line">        System.out.println(<span class="string">&quot;Consumer Started.&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>总体上，我的思路就是这样，希望大家一起讨论学习。</p>
<h2 id="链接"><a href="#链接" class="headerlink" title="链接"></a>链接</h2><ul>
<li><p><a target="_blank" rel="noopener" href="https://github.com/apache/rocketmq/blob/master/docs/cn/RocketMQ_Example.md#1%E5%88%9B%E5%BB%BA%E4%BA%8B%E5%8A%A1%E6%80%A7%E7%94%9F%E4%BA%A7%E8%80%85">GIthub 官方 消息事务样例</a></p>
<p>除了消息事务，还有其他消息发送的样例</p>
</li>
<li><p><a target="_blank" rel="noopener" href="https://blog.csdn.net/Little_fxc/article/details/123923938?spm=1001.2014.3001.5501">自定义脚本启动搭建的RocketMQ伪集群</a></p>
<p>简单的 RocketMQ 的部署方式讲解，及一键启动脚本，方便测试。</p>
</li>
</ul>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
    <link itemprop="mainEntityOfPage" href="http://littlefxc.github.io/2022/04/02/docs/%E5%BE%AE%E6%9C%8D%E5%8A%A1/%E8%87%AA%E5%AE%9A%E4%B9%89%E8%84%9A%E6%9C%AC%E5%90%AF%E5%8A%A8%E6%90%AD%E5%BB%BA%E7%9A%84RocketMQ%E4%BC%AA%E9%9B%86%E7%BE%A4/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="一年春又来">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="一年春又来">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/2022/04/02/docs/%E5%BE%AE%E6%9C%8D%E5%8A%A1/%E8%87%AA%E5%AE%9A%E4%B9%89%E8%84%9A%E6%9C%AC%E5%90%AF%E5%8A%A8%E6%90%AD%E5%BB%BA%E7%9A%84RocketMQ%E4%BC%AA%E9%9B%86%E7%BE%A4/" class="post-title-link" itemprop="url">自定义脚本启动搭建的RocketMQ伪集群</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">发表于</span>

      <time title="创建时间：2022-04-02 02:47:11" itemprop="dateCreated datePublished" datetime="2022-04-02T02:47:11+08:00">2022-04-02</time>
    </span>
      <span class="post-meta-item">
        <span class="post-meta-item-icon">
          <i class="far fa-calendar-check"></i>
        </span>
        <span class="post-meta-item-text">更新于</span>
        <time title="修改时间：2022-05-10 09:27:12" itemprop="dateModified" datetime="2022-05-10T09:27:12+08:00">2022-05-10</time>
      </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">分类于</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F/" itemprop="url" rel="index"><span itemprop="name">分布式系统</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>工欲善其事，必先利其器。</p>
<p>因为只有一台电脑，只能搭建伪集群来学习了，但是，本身又是个偷懒的人，启动伪集群 RocketMQ 的命令有点多，不想敲那么多的命令，顺便将搭建 RocketMQ 集群的部署方式记录一下。</p>
<p>RocketMQ 的部署方式有3种：</p>
<ul>
<li>2m-noslave：多 Master 模式，无 Slave。[双主模式]<ul>
<li>优点：配置简单，性能最高</li>
<li>缺点：可能会有少量消息丢失（配置相关），单台机器重启或宕机期间，该机器下未被消费的消息在机器恢复前不可订阅，影响消息实时性</li>
</ul>
</li>
<li>2m-2s-sync：多 Master 多 Slave 模式，同步双写。[双主双从+同步模式]<ul>
<li>优点：服务可用性与数据可用性非常高</li>
<li>缺点：性能比异步集群略低</li>
</ul>
</li>
<li>2m-2s-async：多 Master 多 Slave 模式，异步复制。[双主双从+异步模式]<ul>
<li>优点：性能同多Master几乎一样，实时性高，主备间切换对应用透明，不需人工干预</li>
<li>缺点：Master宕机或磁盘损坏时会有少量消息丢失</li>
</ul>
</li>
</ul>
<p>本文主要记录一下同步双写模式的搭建步骤和一键启动脚本。</p>
<h2 id="端口规划"><a href="#端口规划" class="headerlink" title="端口规划"></a>端口规划</h2><p>首先，因为只有一台电脑，端口要进行规划，以避免端口占用的问题。</p>
<p>采用的方案就是“双主双从+同步模式”。</p>
<table>
<thead>
<tr>
<th>名称</th>
<th>端口</th>
</tr>
</thead>
<tbody><tr>
<td>namesrv1</td>
<td>9876</td>
</tr>
<tr>
<td>Namesrv2</td>
<td>9877</td>
</tr>
<tr>
<td>brokera-master</td>
<td>10910</td>
</tr>
<tr>
<td>brokera-slave</td>
<td>10920</td>
</tr>
<tr>
<td>brokerb-master</td>
<td>10930</td>
</tr>
<tr>
<td>brokerb-slave</td>
<td>10940</td>
</tr>
</tbody></table>
<h2 id="下载"><a href="#下载" class="headerlink" title="下载"></a>下载</h2><p><a target="_blank" rel="noopener" href="https://www.apache.org/dyn/closer.cgi?path=rocketmq/4.9.3/rocketmq-all-4.9.3-bin-release.zip">https://www.apache.org/dyn/closer.cgi?path=rocketmq/4.9.3/rocketmq-all-4.9.3-bin-release.zip</a></p>
<h2 id="配置文件"><a href="#配置文件" class="headerlink" title="配置文件"></a>配置文件</h2><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vim rocketmq/conf/2m-2s-sync/broker-a.properties</span><br></pre></td></tr></table></figure>

<figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 集群名字</span></span><br><span class="line"><span class="string">brokerClusterName=DefaultCluster</span></span><br><span class="line"><span class="comment"># broker 名字，可重复，master 的名字和 slave 的名字保持一致</span></span><br><span class="line"><span class="string">brokerName=broker-a</span></span><br><span class="line"><span class="comment"># 0 表示 master, &gt;0 表示 slave </span></span><br><span class="line"><span class="string">brokerId=0</span></span><br><span class="line"><span class="comment"># 删除文件时间点，默认凌晨 4点</span></span><br><span class="line"><span class="string">deleteWhen=04</span></span><br><span class="line"><span class="comment"># 文件保留时间，默认 48 小时</span></span><br><span class="line"><span class="string">fileReservedTime=48</span></span><br><span class="line"><span class="comment"># Broker 的角色</span></span><br><span class="line"><span class="string">brokerRole=SYNC_MASTER</span></span><br><span class="line"><span class="comment"># 刷盘方式</span></span><br><span class="line"><span class="string">flushDiskType=ASYNC_FLUSH</span></span><br><span class="line"><span class="comment"># Broker 对外服务的监听端口, 注意需改端口, 并且要和默认的10911相差5以上</span></span><br><span class="line"><span class="string">listenPort=10910</span></span><br><span class="line"><span class="comment"># 是否允许 Broker 自动创建Topic，建议线下开启，线上关闭</span></span><br><span class="line"><span class="string">autoCreateTopicEnable=true</span></span><br><span class="line"><span class="comment"># 是否允许 Broker 自动创建订阅组，建议线下开启，线上关闭</span></span><br><span class="line"><span class="string">autoCreateSubscriptionGroup=true</span></span><br><span class="line"><span class="comment"># nameServer地址，分号分割</span></span><br><span class="line"><span class="string">namesrvAddr=localhost:9876;localhost:9877</span></span><br><span class="line"><span class="comment"># 存储路径</span></span><br><span class="line"><span class="string">storePathRootDir=/usr/local/var/lib/rocketmq/broker-a</span></span><br><span class="line"><span class="comment"># commitLog 存储路径</span></span><br><span class="line"><span class="string">storePathCommitLog=/usr/local/var/lib/rocketmq/broker-a/commitlog</span></span><br><span class="line"><span class="comment"># 消费队列存储路径存储路径</span></span><br><span class="line"><span class="string">storePathConsumeQueue=/usr/local/var/lib/rocketmq/broker-a/consumequeue</span></span><br><span class="line"><span class="comment"># 消息索引存储路径</span></span><br><span class="line"><span class="string">storePathIndex=/usr/local/var/lib/rocketmq/broker-a/index</span></span><br><span class="line"><span class="comment"># checkpoint 文件存储路径</span></span><br><span class="line"><span class="string">storeCheckpoint=/usr/local/var/lib/rocketmq/broker-a/checkpoint</span></span><br><span class="line"><span class="comment"># abort 文件存储路径</span></span><br><span class="line"><span class="string">abortFile=/usr/local/var/lib/rocketmq/broker-a/about</span></span><br></pre></td></tr></table></figure>

<p>其它节点的配置文件类似只需修改一下 <code>brokerId</code>、 <code>brokerName</code>、<code>listenPort</code>、<code>brokerRole</code>和<code>store*</code>这些参数即可。</p>
<h2 id="修改-JVM-启动资源要素"><a href="#修改-JVM-启动资源要素" class="headerlink" title="修改 JVM 启动资源要素"></a>修改 JVM 启动资源要素</h2><p>还是因为本身只有一台机器，资源有限的原因。</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vim rocketmq-4.9.3/bin/runbroker.sh</span><br></pre></td></tr></table></figure>

<p><img src="/Users/fengxuechao/Library/Application%20Support/typora-user-images/image-20220402143803612.png" alt="image-20220402143803612"></p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vim rocketmq-4.9.3/bin/runserver.sh</span><br></pre></td></tr></table></figure>

<p><img src="/Users/fengxuechao/Library/Application%20Support/typora-user-images/image-20220402143514440.png" alt="image-20220402143514440"></p>
<h2 id="一键启动脚本"><a href="#一键启动脚本" class="headerlink" title="一键启动脚本"></a>一键启动脚本</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"><span class="built_in">export</span> JAVA_HOME=<span class="string">&quot;/Library/Java/JavaVirtualMachines/jdk1.8.0_251.jdk/Contents/Home&quot;</span></span><br><span class="line"><span class="built_in">export</span> ROCKETMQ_HOME=<span class="string">&quot;/usr/local/rocketmq-4.9.3&quot;</span></span><br><span class="line"><span class="built_in">export</span> ROCKETMQ_LOG_DIR=<span class="string">&quot;/usr/local/var/log/rocketmq&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> [ <span class="variable">$1</span> == <span class="string">&quot;startall&quot;</span> ] <span class="keyword">then</span></span><br><span class="line">    <span class="comment"># 启动 namesrv1</span></span><br><span class="line">    nohup sh <span class="variable">$&#123;ROCKETMQ_HOME&#125;</span>/bin/mqnamesrv -c <span class="variable">$&#123;ROCKETMQ_HOME&#125;</span>/conf/2m-2s-sync/namesrv-1.properties &gt; <span class="variable">$&#123;ROCKETMQ_LOG_DIR&#125;</span>/mqnamesrv1.log 2&gt;&amp;1 &amp;</span><br><span class="line">    </span><br><span class="line">    <span class="comment"># 启动 namesrv2</span></span><br><span class="line">    nohup sh <span class="variable">$&#123;ROCKETMQ_HOME&#125;</span>/bin/mqnamesrv -c <span class="variable">$&#123;ROCKETMQ_HOME&#125;</span>/conf/2m-2s-sync/namesrv-2.properties &gt; <span class="variable">$&#123;ROCKETMQ_LOG_DIR&#125;</span>/mqnamesrv2.log 2&gt;&amp;1 &amp;</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 启动 broker-a</span></span><br><span class="line">    nohup sh <span class="variable">$&#123;ROCKETMQ_HOME&#125;</span>/bin/mqbroker -c <span class="variable">$&#123;ROCKETMQ_HOME&#125;</span>/conf/2m-2s-sync/broker-a.properties &gt; <span class="variable">$&#123;ROCKETMQ_LOG_DIR&#125;</span>/broker-a.log 2&gt;&amp;1 &amp;</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 启动 broker-a-s</span></span><br><span class="line">    nohup sh <span class="variable">$&#123;ROCKETMQ_HOME&#125;</span>/bin/mqbroker -c <span class="variable">$&#123;ROCKETMQ_HOME&#125;</span>/conf/2m-2s-sync/broker-a-s.properties &gt; <span class="variable">$&#123;ROCKETMQ_LOG_DIR&#125;</span>/broker-a-s.log 2&gt;&amp;1 &amp;</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 启动 broker-b</span></span><br><span class="line">    nohup sh <span class="variable">$&#123;ROCKETMQ_HOME&#125;</span>/bin/mqbroker -c <span class="variable">$&#123;ROCKETMQ_HOME&#125;</span>/conf/2m-2s-sync/broker-b.properties &gt; <span class="variable">$&#123;ROCKETMQ_LOG_DIR&#125;</span>/broker-b.log 2&gt;&amp;1 &amp;</span><br><span class="line"></span><br><span class="line">    <span class="comment"># 启动 broker-b-s</span></span><br><span class="line">    nohup sh <span class="variable">$&#123;ROCKETMQ_HOME&#125;</span>/bin/mqbroker -c <span class="variable">$&#123;ROCKETMQ_HOME&#125;</span>/conf/2m-2s-sync/broker-b-s.properties &gt; <span class="variable">$&#123;ROCKETMQ_LOG_DIR&#125;</span>/broker-b-s.log 2&gt;&amp;1 &amp;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">export</span> NAMESRV_ADDR=localhost:9876,localhost:9877</span><br><span class="line"></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">&quot;rocketmq 集群启动成功!&quot;</span></span><br><span class="line"><span class="keyword">elif</span>[ <span class="variable">$1</span> == <span class="string">&quot;start&quot;</span> ] <span class="keyword">then</span> </span><br><span class="line">    nohup <span class="variable">$&#123;ROCKETMQ_HOME&#125;</span>/bin/mqnamesrv &amp;</span><br><span class="line">    nohup <span class="variable">$&#123;ROCKETMQ_HOME&#125;</span>/bin/mqbroker -n localhost:9876 &amp;</span><br><span class="line">    <span class="built_in">export</span> NAMESRV_ADDR=localhost:9876</span><br><span class="line">    <span class="built_in">echo</span> <span class="string">&quot;rocketmq 单机启动成功!&quot;</span></span><br><span class="line"><span class="keyword">elif</span> [ <span class="variable">$1</span> == <span class="string">&quot;stop&quot;</span> ] <span class="keyword">then</span> </span><br><span class="line">    <span class="comment"># 先关闭 broker</span></span><br><span class="line">    sh <span class="variable">$&#123;ROCKETMQ_HOME&#125;</span>/bin/mqshutdown broker</span><br><span class="line">    <span class="comment"># 再关闭 namesrv</span></span><br><span class="line">    sh <span class="variable">$&#123;ROCKETMQ_HOME&#125;</span>/bin/mqshutdown namesrv</span><br><span class="line">    <span class="built_in">echo</span> <span class="string">&quot;rocketmq 关闭成功!&quot;</span></span><br><span class="line"><span class="keyword">else</span> </span><br><span class="line">    <span class="built_in">echo</span> <span class="string">&quot;rockermq_service.sh (startall|start|stop)&quot;</span></span><br><span class="line"><span class="keyword">fi</span></span><br></pre></td></tr></table></figure>

<h2 id="运行情况"><a href="#运行情况" class="headerlink" title="运行情况"></a>运行情况</h2><p><img src="/Users/fengxuechao/Library/Application%20Support/typora-user-images/image-20220402163857315.png" alt="image-20220402163857315"></p>
<h2 id="参考文档"><a href="#参考文档" class="headerlink" title="参考文档"></a>参考文档</h2><p><a target="_blank" rel="noopener" href="https://github.com/apache/rocketmq/blob/master/docs/cn/operation.md">官方中文部署文档</a></p>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
    <link itemprop="mainEntityOfPage" href="http://littlefxc.github.io/2022/04/01/docs/JVM/JVM-%E6%97%A5%E5%BF%97/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="一年春又来">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="一年春又来">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/2022/04/01/docs/JVM/JVM-%E6%97%A5%E5%BF%97/" class="post-title-link" itemprop="url">JVM 日志</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">发表于</span>

      <time title="创建时间：2022-04-01 07:33:45" itemprop="dateCreated datePublished" datetime="2022-04-01T07:33:45+08:00">2022-04-01</time>
    </span>
      <span class="post-meta-item">
        <span class="post-meta-item-icon">
          <i class="far fa-calendar-check"></i>
        </span>
        <span class="post-meta-item-text">更新于</span>
        <time title="修改时间：2022-05-10 09:24:58" itemprop="dateModified" datetime="2022-05-10T09:24:58+08:00">2022-05-10</time>
      </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">分类于</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/Java/" itemprop="url" rel="index"><span itemprop="name">Java</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <p>JDK8 JVM 垃圾收集日志打印参数</p>
<figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">-Xms50m -Xmx50m -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+PrintGCCause -Xloggc:/Users/fengxuechao/WorkSpace/IdeaProjects/foodie/logs/order_gclog.log</span><br></pre></td></tr></table></figure>

<p>JDK8 运行时参数</p>
<ul>
<li><p><code>-XX:+TraceClassLoading</code>：跟踪类加载情况</p>
</li>
<li><p><code>+XX:TraceBiasedLocking</code>：跟踪偏向锁的情况 </p>
</li>
</ul>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
    <link itemprop="mainEntityOfPage" href="http://littlefxc.github.io/2022/03/22/docs/MySQL/MySQL%E7%B4%A2%E5%BC%95%E5%A4%B1%E6%95%88%E7%9A%84%E5%8E%9F%E5%9B%A0%E8%AE%B0%E5%BD%95%E6%80%BB%E7%BB%93/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="一年春又来">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="一年春又来">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/2022/03/22/docs/MySQL/MySQL%E7%B4%A2%E5%BC%95%E5%A4%B1%E6%95%88%E7%9A%84%E5%8E%9F%E5%9B%A0%E8%AE%B0%E5%BD%95%E6%80%BB%E7%BB%93/" class="post-title-link" itemprop="url">MySQL索引失效的原因记录总结</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">发表于</span>

      <time title="创建时间：2022-03-22 06:55:56" itemprop="dateCreated datePublished" datetime="2022-03-22T06:55:56+08:00">2022-03-22</time>
    </span>
      <span class="post-meta-item">
        <span class="post-meta-item-icon">
          <i class="far fa-calendar-check"></i>
        </span>
        <span class="post-meta-item-text">更新于</span>
        <time title="修改时间：2022-05-10 09:22:58" itemprop="dateModified" datetime="2022-05-10T09:22:58+08:00">2022-05-10</time>
      </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">分类于</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/" itemprop="url" rel="index"><span itemprop="name">数据库</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <h2 id="0-引言"><a href="#0-引言" class="headerlink" title="0 引言"></a>0 引言</h2><p>好记性不如烂笔头，把常见的一些 MySQL 索引失效的问题记录下来，在工作中可以时时检查对比。</p>
<p>主要分为两个部分，explain 介绍和各种索引失效场景的模拟。</p>
<h3 id="建表语句"><a href="#建表语句" class="headerlink" title="建表语句"></a>建表语句</h3><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> <span class="keyword">TABLE</span> `people` (</span><br><span class="line">  `id` <span class="type">int</span> unsigned <span class="keyword">NOT</span> <span class="keyword">NULL</span> AUTO_INCREMENT,</span><br><span class="line">  `name` <span class="type">varchar</span>(<span class="number">30</span>) <span class="type">CHARACTER</span> <span class="keyword">SET</span> utf8mb4 <span class="keyword">COLLATE</span> utf8mb4_0900_ai_ci <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;姓名&#x27;</span>,</span><br><span class="line">  `gender` tinyint unsigned <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;性别，0男1女&#x27;</span>,</span><br><span class="line">  `career` <span class="type">varchar</span>(<span class="number">30</span>) <span class="type">CHARACTER</span> <span class="keyword">SET</span> utf8mb4 <span class="keyword">COLLATE</span> utf8mb4_0900_ai_ci <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;技能&#x27;</span>,</span><br><span class="line">  `skills` <span class="type">varchar</span>(<span class="number">30</span>) <span class="keyword">COLLATE</span> utf8mb4_general_ci <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;技能&#x27;</span>,</span><br><span class="line">  `birthday` <span class="type">date</span> <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;出生日期&#x27;</span>,</span><br><span class="line">  `lifetime` <span class="type">int</span> <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;寿命&#x27;</span>,</span><br><span class="line">  `gmt_create` <span class="type">timestamp</span> <span class="keyword">NULL</span> <span class="keyword">DEFAULT</span> <span class="keyword">NULL</span> COMMENT <span class="string">&#x27;入库时间&#x27;</span>,</span><br><span class="line">  <span class="keyword">PRIMARY</span> KEY (`id`),</span><br><span class="line">  KEY `idx_career_skills_lifetime` (`career`,`skills`,`lifetime`) <span class="keyword">USING</span> BTREE</span><br><span class="line">) ENGINE<span class="operator">=</span>InnoDB AUTO_INCREMENT<span class="operator">=</span><span class="number">26</span> <span class="keyword">DEFAULT</span> CHARSET<span class="operator">=</span>utf8mb4 <span class="keyword">COLLATE</span><span class="operator">=</span>utf8mb4_general_ci;</span><br></pre></td></tr></table></figure>

<h3 id="explain-执行计划各属性解释说明"><a href="#explain-执行计划各属性解释说明" class="headerlink" title="explain 执行计划各属性解释说明"></a><code>explain</code> 执行计划各属性解释说明</h3><h4 id="id"><a href="#id" class="headerlink" title="id"></a>id</h4><p>SELECT 识别符。这是 SELECT 的查询序列号，SQL 的执行顺序：</p>
<ol>
<li>id 相同时，执行顺序自上而下。</li>
<li>如果是子查询，id 会递增，id 值越大优先级越高，越先被执行。</li>
<li>id 如果相同，可以认为是一组，自上而下顺序执行；再所有组中，优先级越高，越先被执行。</li>
</ol>
<h4 id="select-type"><a href="#select-type" class="headerlink" title="select_type"></a>select_type</h4><table>
<thead>
<tr>
<th>select_type</th>
<th>说明</th>
</tr>
</thead>
<tbody><tr>
<td>SIMPLE</td>
<td>简单查询，不使用UNION或子查询</td>
</tr>
<tr>
<td>PRIMARY</td>
<td>最外层查询，查询中若包含任何复杂的子部分，最外层的 select 被标记为 PRIMARY</td>
</tr>
<tr>
<td>SUBQUERY</td>
<td>子查询中的第一个select，结果不依赖于外部查询</td>
</tr>
<tr>
<td>DEPENDENT SUBQUERY</td>
<td>子查询中的第一个select，结果依赖于外部查询</td>
</tr>
<tr>
<td>UNCACHEABLE SUBQUERY</td>
<td>一个子查询的结果不能被缓存，必需重新评估外链表的第一行</td>
</tr>
<tr>
<td>DERIVED</td>
<td>子查询，派生表的 select，from 子句的的子查询</td>
</tr>
<tr>
<td>UNION</td>
<td>联合，UNION 中的第二个或后面的SELECT语句</td>
</tr>
<tr>
<td>UNION RESULT</td>
<td>使用联合的结果</td>
</tr>
<tr>
<td>DEPENDENT UNION</td>
<td>UNION中的第二个或后面的SELECT语句，取决于外面的查询</td>
</tr>
</tbody></table>
<h4 id="type"><a href="#type" class="headerlink" title="type"></a>type</h4><table>
<thead>
<tr>
<th>type</th>
<th>说明</th>
</tr>
</thead>
<tbody><tr>
<td>ALL</td>
<td>全数据表扫描</td>
</tr>
<tr>
<td>index</td>
<td>全索引表扫描</td>
</tr>
<tr>
<td>RANGE</td>
<td>对索引列进行范围查询</td>
</tr>
<tr>
<td>INDEX_MERGE</td>
<td>合并索引，使用多个单列索引查询</td>
</tr>
<tr>
<td>REF</td>
<td>根据索引查找一个或多个值</td>
</tr>
<tr>
<td>EQ_REF</td>
<td>搜索时使用 primary key 或 unique 类型</td>
</tr>
<tr>
<td>CONST</td>
<td>常量，表最多有一个匹配行,因为仅有一行,在这行的列值可被优化器剩余部分认为是常数,const表很快,因为它们只读取一次。</td>
</tr>
<tr>
<td>SYSTEM</td>
<td>系统，表仅有一行(=系统表)。这是const联接类型的一个特例。</td>
</tr>
</tbody></table>
<ul>
<li><p>性能：all  &lt;  index  &lt;  range  &lt;  index_merge  &lt;  ref_or_null  &lt;  ref  &lt;  eq_ref  &lt;  system/const</p>
</li>
<li><p>性能在 range 之下基本都可以进行调优</p>
</li>
</ul>
<h4 id="possible-keys"><a href="#possible-keys" class="headerlink" title="possible_keys"></a>possible_keys</h4><p>可能使用的索引</p>
<h4 id="key"><a href="#key" class="headerlink" title="key"></a>key</h4><p>真实使用的索引</p>
<h4 id="key-len"><a href="#key-len" class="headerlink" title="key_len"></a>key_len</h4><p>MySQL中使用索引字节长度</p>
<h4 id="rows"><a href="#rows" class="headerlink" title="rows"></a>rows</h4><p>mysql 预估为了找到所需的行而要读取的行数</p>
<h4 id="filtered"><a href="#filtered" class="headerlink" title="filtered"></a>filtered</h4><p>按表条件过滤的行百分比</p>
<h4 id="extra"><a href="#extra" class="headerlink" title="extra"></a>extra</h4><table>
<thead>
<tr>
<th align="left">extra</th>
<th align="left">说明</th>
</tr>
</thead>
<tbody><tr>
<td align="left">Using index</td>
<td align="left">此值表示mysql将使用覆盖索引，以避免访问表。</td>
</tr>
<tr>
<td align="left">Using where</td>
<td align="left">mysql 将在存储引擎检索行后再进行过滤，许多where条件里涉及索引中的列，当(并且如果)它读取索引时，就能被存储引擎检验，因此不是所有带where子句的查询都会显示“Using where”。有时“Using where”的出现就是一个暗示：查询可受益于不同的索引。</td>
</tr>
<tr>
<td align="left">Using temporary</td>
<td align="left">mysql 对查询结果排序时会使用临时表。常见于排序和分组查询<code>group by</code>,<code>order by</code></td>
</tr>
<tr>
<td align="left">Using filesort</td>
<td align="left">当Query中包含 order by 操作，而且无法利用索引完成的排序操作称为“文件排序”。</td>
</tr>
<tr>
<td align="left">Range checked for each record(index map: N)</td>
<td align="left">没有好用的索引，新的索引将在联接的每一行上重新估算，N是显示在possible_keys列中索引的位图，并且是冗余的</td>
</tr>
<tr>
<td align="left">…</td>
<td align="left">…</td>
</tr>
</tbody></table>
<h2 id="1-不符合最左前缀原则"><a href="#1-不符合最左前缀原则" class="headerlink" title="1 不符合最左前缀原则"></a>1 不符合最左前缀原则</h2><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">mysql<span class="operator">&gt;</span> explain <span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> people <span class="keyword">where</span> `lifetime` <span class="operator">=</span> <span class="number">23</span> <span class="keyword">and</span> `skills` <span class="operator">=</span> <span class="string">&#x27;口才&#x27;</span>;</span><br><span class="line"><span class="operator">+</span><span class="comment">----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+</span></span><br><span class="line"><span class="operator">|</span> id <span class="operator">|</span> select_type <span class="operator">|</span> <span class="keyword">table</span>  <span class="operator">|</span> partitions <span class="operator">|</span> type <span class="operator">|</span> possible_keys <span class="operator">|</span> key  <span class="operator">|</span> key_len <span class="operator">|</span> <span class="keyword">ref</span>  <span class="operator">|</span> <span class="keyword">rows</span> <span class="operator">|</span> filtered <span class="operator">|</span> Extra       <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+</span></span><br><span class="line"><span class="operator">|</span>  <span class="number">1</span> <span class="operator">|</span> SIMPLE      <span class="operator">|</span> people <span class="operator">|</span> <span class="keyword">NULL</span>       <span class="operator">|</span> <span class="keyword">ALL</span>  <span class="operator">|</span> <span class="keyword">NULL</span>          <span class="operator">|</span> <span class="keyword">NULL</span> <span class="operator">|</span> <span class="keyword">NULL</span>    <span class="operator">|</span> <span class="keyword">NULL</span> <span class="operator">|</span>   <span class="number">14</span> <span class="operator">|</span>     <span class="number">7.14</span> <span class="operator">|</span> <span class="keyword">Using</span> <span class="keyword">where</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+</span></span><br><span class="line"><span class="number">1</span> <span class="type">row</span> <span class="keyword">in</span> <span class="keyword">set</span>, <span class="number">1</span> warning (<span class="number">0.00</span> sec)</span><br></pre></td></tr></table></figure>

<p>说明：</p>
<ul>
<li><p>最左前缀原则指的是从索引最左前列开始并且不跳过索引中的列</p>
</li>
<li><p>type 是 ALL, 表示查询语句是全表数据查询，where 的查询条件是 lifetime 和 skills，缺少 career 这个索引条件，无法命中索引 idx_career_skills_lifetime。</p>
</li>
</ul>
<h2 id="2-在索引列上有多余操作，如：函数、计算、类型转换"><a href="#2-在索引列上有多余操作，如：函数、计算、类型转换" class="headerlink" title="2 在索引列上有多余操作，如：函数、计算、类型转换"></a>2 在索引列上有多余操作，如：函数、计算、类型转换</h2><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">mysql<span class="operator">&gt;</span> explain <span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> people <span class="keyword">where</span> <span class="keyword">left</span>(career, <span class="number">2</span>) <span class="operator">=</span> <span class="string">&#x27;群众&#x27;</span>;</span><br><span class="line"><span class="operator">+</span><span class="comment">----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+</span></span><br><span class="line"><span class="operator">|</span> id <span class="operator">|</span> select_type <span class="operator">|</span> <span class="keyword">table</span>  <span class="operator">|</span> partitions <span class="operator">|</span> type <span class="operator">|</span> possible_keys <span class="operator">|</span> key  <span class="operator">|</span> key_len <span class="operator">|</span> <span class="keyword">ref</span>  <span class="operator">|</span> <span class="keyword">rows</span> <span class="operator">|</span> filtered <span class="operator">|</span> Extra       <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+</span></span><br><span class="line"><span class="operator">|</span>  <span class="number">1</span> <span class="operator">|</span> SIMPLE      <span class="operator">|</span> people <span class="operator">|</span> <span class="keyword">NULL</span>       <span class="operator">|</span> <span class="keyword">ALL</span>  <span class="operator">|</span> <span class="keyword">NULL</span>          <span class="operator">|</span> <span class="keyword">NULL</span> <span class="operator">|</span> <span class="keyword">NULL</span>    <span class="operator">|</span> <span class="keyword">NULL</span> <span class="operator">|</span>   <span class="number">14</span> <span class="operator">|</span>   <span class="number">100.00</span> <span class="operator">|</span> <span class="keyword">Using</span> <span class="keyword">where</span> <span class="operator">|</span></span><br><span class="line"><span class="operator">+</span><span class="comment">----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-------------+</span></span><br></pre></td></tr></table></figure>

<p>说明：</p>
<ul>
<li><code>LEFT()</code>函数是一个字符串函数，它返回具有指定长度的字符串的左边部分。</li>
<li><code>LEFT()</code>函数的语法：<code>LEFT(str,length);</code><ul>
<li><code>str</code>是要提取子字符串的字符串。</li>
<li><code>length</code>是一个正整数，指定将从左边返回的字符数。</li>
<li><code>LEFT()</code>函数返回<code>str</code>字符串中最左边的长度字符。</li>
<li>如果<code>str</code>或<code>length</code>参数为<code>NULL</code>，则返回<code>NULL</code>值。</li>
<li>如果<code>length</code>为<code>0</code>或为负，则<code>LEFT</code>函数返回一个空字符串。</li>
<li>如果<code>length</code>大于<code>str</code>字符串的长度，则<code>LEFT</code>函数返回整个<code>str</code>字符串。</li>
<li>请注意，<code>SUBSTRING</code> 或 <code>SUBSTR</code> 函数也提供与<code>LEFT</code>函数相同的功能。</li>
</ul>
</li>
<li>career 字段是有索引的，但是执行计划中看到的却没有命中，说明在索引字段上添加多余操作会使其失效</li>
</ul>
<h2 id="3-查询条件有-、-gt-、-lt-等"><a href="#3-查询条件有-、-gt-、-lt-等" class="headerlink" title="3 查询条件有!=、&gt;    、&lt;等"></a>3 查询条件有<code>!=</code>、<code>&gt;</code>    、<code>&lt;</code>等</h2><h2 id="4-like以通配符-为开头"><a href="#4-like以通配符-为开头" class="headerlink" title="4 like以通配符%为开头"></a>4 <code>like</code>以通配符<code>%</code>为开头</h2><h2 id="5-使用来了or"><a href="#5-使用来了or" class="headerlink" title="5 使用来了or"></a>5 使用来了<code>or</code></h2><h2 id="6-使用了is-null或者is-not-null"><a href="#6-使用了is-null或者is-not-null" class="headerlink" title="6 使用了is null或者is not null"></a>6 使用了<code>is null</code>或者<code>is not null</code></h2>
      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
    <link itemprop="mainEntityOfPage" href="http://littlefxc.github.io/2022/03/18/docs/%E5%BE%AE%E6%9C%8D%E5%8A%A1/Redis/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="一年春又来">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="一年春又来">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/2022/03/18/docs/%E5%BE%AE%E6%9C%8D%E5%8A%A1/Redis/" class="post-title-link" itemprop="url">Redis</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">发表于</span>
      

      <time title="创建时间：2022-03-18 03:47:41 / 修改时间：12:00:39" itemprop="dateCreated datePublished" datetime="2022-03-18T03:47:41+08:00">2022-03-18</time>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <h2 id="redis-常见问题"><a href="#redis-常见问题" class="headerlink" title="redis 常见问题"></a>redis 常见问题</h2><ol>
<li>为什么使用 redis ？<ul>
<li>高性能、高并发</li>
<li>redis 走内存，天然支持高并发，单机轻松支持一秒十几万，是单机mysql性能的几十倍。</li>
</ul>
</li>
<li>redis 在项目中如何使用？</li>
</ol>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
    <link itemprop="mainEntityOfPage" href="http://littlefxc.github.io/2022/03/18/docs/MySQL/%E4%BB%80%E4%B9%88%E6%98%AFMySQL%E7%9A%84%E5%9B%9E%E8%A1%A8/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="一年春又来">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="一年春又来">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/2022/03/18/docs/MySQL/%E4%BB%80%E4%B9%88%E6%98%AFMySQL%E7%9A%84%E5%9B%9E%E8%A1%A8/" class="post-title-link" itemprop="url">什么是MySQL的回表?</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">发表于</span>

      <time title="创建时间：2022-03-18 02:41:19" itemprop="dateCreated datePublished" datetime="2022-03-18T02:41:19+08:00">2022-03-18</time>
    </span>
      <span class="post-meta-item">
        <span class="post-meta-item-icon">
          <i class="far fa-calendar-check"></i>
        </span>
        <span class="post-meta-item-text">更新于</span>
        <time title="修改时间：2022-05-10 09:23:09" itemprop="dateModified" datetime="2022-05-10T09:23:09+08:00">2022-05-10</time>
      </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">分类于</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/" itemprop="url" rel="index"><span itemprop="name">数据库</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <p>[toc]</p>
<h1 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h1><p>简单来说，回表就是 MySQL 要先查询到主键索引，然后再用主键索引定位到数据。</p>
<p>下面，对一些问题进行分析与回答：</p>
<ul>
<li>什么是聚簇索引？什么是非聚簇索引？</li>
<li>为什么回表要先查到主键索引？</li>
<li>主键索引和非主键索引有什么区别？</li>
<li>如何避免回表？</li>
</ul>
<h1 id="聚簇索引和非聚簇索引是什么？"><a href="#聚簇索引和非聚簇索引是什么？" class="headerlink" title="聚簇索引和非聚簇索引是什么？"></a>聚簇索引和非聚簇索引是什么？</h1><p>MySQL 的索引有不同的角度的分类方式，例如：按数据结构分、按逻辑角度分、按物理存储分。</p>
<p>其中，按物理存储分有两种索引：<strong>聚簇索引</strong>和<strong>非聚簇索引</strong>。</p>
<p>简单来说，<strong>聚簇索引是主键索引</strong>。</p>
<p><strong>主键索引之外的就是非聚簇索引</strong>，非聚簇索引又叫辅助索引或者二级索引。</p>
<h1 id="主键索引和非主键索引有什么区别？"><a href="#主键索引和非主键索引有什么区别？" class="headerlink" title="主键索引和非主键索引有什么区别？"></a>主键索引和非主键索引有什么区别？</h1><p>相同点：都使用的是 B+Tree 。</p>
<p>不同点：叶子节点存储的数据不同</p>
<ul>
<li>主键索引的叶子节点存储的是<strong>一行完整的数据</strong>；</li>
<li>非主键索引的叶子节点存储的是<strong>主键值</strong>。叶子节点不包含记录的全部数据，非主键的叶子节点除了用来排序的 key 还包含一个书签（bookmark），其中存储了聚簇索引的 key。</li>
</ul>
<p>那么这两种索引在使用方面上有什么区别呢？</p>
<ol>
<li>使用主键索引查询 <figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># 主键索引的的叶子节点存储的是<span class="operator">*</span><span class="operator">*</span>一行完整的数据<span class="operator">*</span><span class="operator">*</span>，</span><br><span class="line"># 所以只需搜索主键索引的 B<span class="operator">+</span>Tree 就可以轻松找到全部数据</span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> <span class="keyword">user</span> <span class="keyword">where</span> id <span class="operator">=</span> <span class="number">1</span>;</span><br></pre></td></tr></table></figure>
</li>
<li>使用非主键索引查询 <figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"># 非主键索引的叶子节点存储的是<span class="operator">*</span><span class="operator">*</span>主键值<span class="operator">*</span><span class="operator">*</span>，</span><br><span class="line"># 所以MySQL会先查询到 name 列的索引的 B<span class="operator">+</span>Tree，搜索得到对应的主键值</span><br><span class="line"># 然后再去搜索该主键值查询主键索引的 B<span class="operator">+</span>Tree 才可以找到对应的数据</span><br><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> <span class="keyword">user</span> <span class="keyword">where</span> name <span class="operator">=</span> <span class="string">&#x27;Jack&#x27;</span>;</span><br></pre></td></tr></table></figure>
</li>
</ol>
<p>可以看出使用非主键索引要比主键索引多使用一次 B+Tree。</p>
<h2 id="B-Tree-和-B-Tree-的简单理解"><a href="#B-Tree-和-B-Tree-的简单理解" class="headerlink" title="B-Tree 和 B+Tree 的简单理解"></a>B-Tree 和 B+Tree 的简单理解</h2><p>理解聚簇索引和非聚簇索引的关键在于 B+Tree 的理解。</p>
<p>用一幅图来表示，其它的就不再过多解释了：</p>
<p><img src="https://gitee.com/littlefxc/oss/raw/master/images/8ec43769f81ea5e901077f047b8da974.png" alt="https://gitee.com/littlefxc/oss/raw/master/images/KjXSSU.jpg"></p>
<p>这里只是简单介绍一下 B-Tree 和 B+Tree 的区别：</p>
<ol>
<li>B+树中只有叶子节点会带有指向记录的指针，而B树则所有节点都带有，在内部节点出现的索引项不会再出现在叶子节点中。</li>
<li>B+树中所有叶子节点都是通过指针连接在一起，而B树不会。</li>
</ol>
<h1 id="如何避免回表？"><a href="#如何避免回表？" class="headerlink" title="如何避免回表？"></a>如何避免回表？</h1><p>使用覆盖索引，<strong>所谓覆盖索引就是指索引中包含了查询中的所有字段，这种情况下就不需要再进行回表查询了。</strong></p>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




    


<div class="post-block">
  
  

  <article itemscope itemtype="http://schema.org/Article" class="post-content" lang="">
    <link itemprop="mainEntityOfPage" href="http://littlefxc.github.io/2022/03/16/docs/JVM/JVM-JDK8%E7%9A%84JVM%E5%8F%82%E6%95%B0/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.gif">
      <meta itemprop="name" content="一年春又来">
      <meta itemprop="description" content="">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="一年春又来">
    </span>
      <header class="post-header">
        <h2 class="post-title" itemprop="name headline">
          <a href="/2022/03/16/docs/JVM/JVM-JDK8%E7%9A%84JVM%E5%8F%82%E6%95%B0/" class="post-title-link" itemprop="url">JVM JDK8的JVM参数</a>
        </h2>

        <div class="post-meta-container">
          <div class="post-meta">
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-calendar"></i>
      </span>
      <span class="post-meta-item-text">发表于</span>

      <time title="创建时间：2022-03-16 09:08:55" itemprop="dateCreated datePublished" datetime="2022-03-16T09:08:55+08:00">2022-03-16</time>
    </span>
      <span class="post-meta-item">
        <span class="post-meta-item-icon">
          <i class="far fa-calendar-check"></i>
        </span>
        <span class="post-meta-item-text">更新于</span>
        <time title="修改时间：2022-05-10 09:25:07" itemprop="dateModified" datetime="2022-05-10T09:25:07+08:00">2022-05-10</time>
      </span>
    <span class="post-meta-item">
      <span class="post-meta-item-icon">
        <i class="far fa-folder"></i>
      </span>
      <span class="post-meta-item-text">分类于</span>
        <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
          <a href="/categories/Java/" itemprop="url" rel="index"><span itemprop="name">Java</span></a>
        </span>
    </span>

  
</div>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">
          <blockquote>
<p>本文参数基于JDK 8整理</p>
</blockquote>
<table>
<thead>
<tr>
<th align="left">收集器</th>
<th>参数及默认值</th>
<th>备注</th>
</tr>
</thead>
<tbody><tr>
<td align="left">Serial</td>
<td>-XX:+UseSerialGC</td>
<td>虚拟机在Client模式下的默认值，开启后，使用 Serial + Serial Old 的组合</td>
</tr>
<tr>
<td align="left">ParNew</td>
<td>-XX:+UseParNewGC</td>
<td>开启后，使用ParNew + Serial Old的组合</td>
</tr>
<tr>
<td align="left"></td>
<td>-XX:ParallelGCThreads=n</td>
<td>设置垃圾收集器在并行阶段使用的垃圾收集线程数，当逻辑处理器数量小于8时，n的值与逻辑处理器数量相同；如果逻辑处理器数量大于8个，则n的值大约为逻辑处理器数量的5/8，大多数情况下是这样，除了较大的SPARC系统，其中n的值约为逻辑处理器的5/16。</td>
</tr>
<tr>
<td align="left">Parallel Scavenge</td>
<td>-XX:+UseParallelGC</td>
<td>虚拟机在Server模式下的默认值，开启后，使用 Parallel Scavenge + Serial Old的组合</td>
</tr>
<tr>
<td align="left"></td>
<td>-XX:MaxGCPauseMillis=n</td>
<td>收集器尽可能保证单次内存回收停顿的时间不超过这个值，但是并不保证不超过该值</td>
</tr>
<tr>
<td align="left"></td>
<td>-XX:GCTimeRatio=n</td>
<td>设置吞吐量的大小，取值范围0-100，假设 GCTimeRatio 的值为 n，那么系统将花费不超过 1/(1+n) 的时间用于垃圾收集</td>
</tr>
<tr>
<td align="left"></td>
<td>-XX:+UseAdaptiveSizePolicy</td>
<td>开启后，无需人工指定新生代的大小（-Xmn）、 Eden和Survisor的比例（-XX:SurvivorRatio）以及晋升老年代对象的年龄（-XX:PretenureSizeThreshold）等参数，收集器会根据当前系统的运行情况自动调整</td>
</tr>
<tr>
<td align="left">Serial Old</td>
<td>无</td>
<td>Serial Old是Serial的老年代版本，主要用于 Client 模式下的老生代收集，同时也是 CMS 在发生 Concurrent Mode Failure时的后备方案</td>
</tr>
<tr>
<td align="left">Parallel Old</td>
<td>-XX:+UseParallelOldGC</td>
<td>开启后，使用Parallel Scavenge + Parallel Old的组合。Parallel Old是Parallel Scavenge的老年代版本，在注重吞吐量和 CPU 资源敏感的场合，可以优先考虑这个组合</td>
</tr>
<tr>
<td align="left">CMS</td>
<td>-XX:+UseConcMarkSweepGC</td>
<td>开启后，使用ParNew + CMS的组合；Serial Old收集器将作为CMS收集器出现 Concurrent Mode Failure 失败后的后备收集器使用</td>
</tr>
<tr>
<td align="left"></td>
<td>-XX:CMSInitiatingOccupancyFraction=68</td>
<td>CMS 收集器在老年代空间被使用多少后触发垃圾收集，默认68%</td>
</tr>
<tr>
<td align="left"></td>
<td>-XX:+UseCMSCompactAtFullCollection</td>
<td>在完成垃圾收集后是否要进行一次内存碎片整理，默认开启</td>
</tr>
<tr>
<td align="left"></td>
<td>-XX:CMSFullGCsBeforeCompaction=0</td>
<td>在进行若干次Full GC后就进行一次内存碎片整理，默认0</td>
</tr>
<tr>
<td align="left"></td>
<td>-XX:+UseCMSInitiatingOccupancyOnly</td>
<td>允许使用占用值作为启动CMS收集器的唯一标准，一般和CMSFullGCsBeforeCompaction配合使用。如果开启，那么当CMSFullGCsBeforeCompaction达到阈值就开始GC，如果关闭，那么JVM仅在第一次使用CMSFullGCsBeforeCompaction的值，后续则自动调整，默认关闭。</td>
</tr>
<tr>
<td align="left"></td>
<td>-XX:+CMSParallelRemarkEnabled</td>
<td>重新标记阶段并行执行，使用此参数可降低标记停顿，默认打开（仅适用于ParNewGC）</td>
</tr>
<tr>
<td align="left"></td>
<td>-XX:+CMSScavengeBeforeRemark</td>
<td>开启或关闭在CMS重新标记阶段之前的清除（YGC）尝试。新生代里一部分对象会作为GC Roots，让CMS在重新标记之前，做一次YGC，而YGC能够回收掉新生代里大多数对象，这样就可以减少GC Roots的开销。因此，打开此开关，可在一定程度上降低CMS重新标记阶段的扫描时间，当然，开启此开关后，YGC也会消耗一些时间。PS. 开启此开关并不保证在标记阶段前一定会进行清除操作，生产环境建议开启，默认关闭。</td>
</tr>
<tr>
<td align="left">CMS-Precleaning</td>
<td>-XX:+CMSPrecleaningEnabled</td>
<td>是否启用并发预清理，默认开启</td>
</tr>
<tr>
<td align="left">CMS-AbortablePreclean</td>
<td>-XX:CMSScheduleRemark EdenSizeThreshold=2M</td>
<td>如果伊甸园的内存使用超过该值，才可能进入“并发可中止的预清理”这个阶段</td>
</tr>
<tr>
<td align="left">CMS-AbortablePreclean</td>
<td>-XX:CMSMaxAbortablePrecleanLoops=0</td>
<td>“并发可终止的预清理阶段”的循环次数，默认0，表示不做限制</td>
</tr>
<tr>
<td align="left">CMS-AbortablePreclean</td>
<td>-XX:+CMSMaxAbortablePrecleanTime=5000</td>
<td>“并发可终止的预清理”阶段持续的最大时间</td>
</tr>
<tr>
<td align="left"></td>
<td>-XX:+CMSClassUnloadingEnabled</td>
<td>使用CMS时，是否启用类卸载，默认开启</td>
</tr>
<tr>
<td align="left"></td>
<td>-XX:+ExplicitGCInvokesConcurrent</td>
<td>显示调用System.gc()会触发Full GC，会有Stop The World，开启此参数后，可让System.gc()触发的垃圾回收变成一次普通的CMS GC。</td>
</tr>
<tr>
<td align="left"></td>
<td>-XX:+UseG1GC</td>
<td>使用G1收集器</td>
</tr>
<tr>
<td align="left"></td>
<td>-XX:G1HeapRegionSize=n</td>
<td>设置每个region的大小，该值为2的幂，范围为1MB到32MB，如不指定G1会根据堆的大小自动决定</td>
</tr>
<tr>
<td align="left"></td>
<td>-XX:MaxGCPauseMillis=200</td>
<td>设置最大停顿时间，默认值为200毫秒。</td>
</tr>
<tr>
<td align="left"></td>
<td>-XX:G1NewSizePercent=5</td>
<td>设置年轻代占整个堆的最小百分比，默认值是5，这是个实验参数。需用-XX:+UnlockExperimentalVMOptions解锁试验参数后，才能使用该参数。</td>
</tr>
<tr>
<td align="left"></td>
<td>-XX:G1MaxNewSizePercent=60</td>
<td>设置年轻代占整个堆的最大百分比，默认值是60，这是个实验参数。需用-XX:+UnlockExperimentalVMOptions解锁试验参数后，才能使用该参数。</td>
</tr>
<tr>
<td align="left"></td>
<td>-XX:ParallelGCThreads=n</td>
<td>设置垃圾收集器在并行阶段使用的垃圾收集线程数，当逻辑处理器数量小于8时，n的值与逻辑处理器数量相同；如果逻辑处理器数量大于8个，则n的值大约为逻辑处理器数量的5/8，大多数情况下是这样，除了较大的SPARC系统，其中n的值约为逻辑处理器的5/16。</td>
</tr>
<tr>
<td align="left"></td>
<td>-XX:ConcGCThreads=n</td>
<td>设置垃圾收集器并发阶段使用的线程数量，设置n大约为ParallelGCThreads的1/4。</td>
</tr>
<tr>
<td align="left"></td>
<td>-XX:InitiatingHeapOccupancyPercent=45</td>
<td>老年代大小达到该阈值，就触发Mixed GC，默认值为45。</td>
</tr>
<tr>
<td align="left"></td>
<td>-XX:G1MixedGCLiveThresholdPercent=85</td>
<td>Region中的对象，活跃度低于该阈值，才可能被包含在Mixed GC收集周期中，默认值为85，这是个实验参数。需用-XX:+UnlockExperimentalVMOptions解锁试验参数后，才能使用该参数。</td>
</tr>
<tr>
<td align="left"></td>
<td>-XX:G1HeapWastePercent=5</td>
<td>设置浪费的堆内存百分比，当可回收百分比小于浪费百分比时，JVM就不会启动Mixed GC，从而避免昂贵的GC开销。此参数相当于用来设置允许垃圾对象占用内存的最大百分比。</td>
</tr>
<tr>
<td align="left"></td>
<td>-XX:G1MixedGCCountTarget=8</td>
<td>设置在标记周期完成之后，最多执行多少次Mixed GC，默认值为8。</td>
</tr>
<tr>
<td align="left"></td>
<td>-XX:G1OldCSetRegionThresholdPercent=10</td>
<td>设置在一次Mixed GC中被收集的老年代的比例上限，默认值是Java堆的10%，这是个实验参数。需用-XX:+UnlockExperimentalVMOptions解锁试验参数后，才能使用该参数。</td>
</tr>
<tr>
<td align="left"></td>
<td>-XX:G1ReservePercent=10</td>
<td>设置预留空闲内存百分比，虚拟机会保证Java堆有这么多空间可用，从而防止对象晋升时无空间可用而失败，默认值为Java堆的10％。</td>
</tr>
<tr>
<td align="left"></td>
<td>-XX:-G1PrintHeapRegions</td>
<td>输出Region被分配和回收的信息，默认false</td>
</tr>
<tr>
<td align="left"></td>
<td>-XX:-G1PrintRegionLivenessInfo</td>
<td>在清理阶段的并发标记环节，输出堆中的所有Regions的活跃度信息，默认false</td>
</tr>
<tr>
<td align="left">Shenandoah</td>
<td>-XX:+UseShenandoahGC</td>
<td>使用UseShenandoahGC，这是个实验参数，需用-XX:+UnlockExperimentalVMOptions解锁试验参数后，才能使用该参数；另外该参数只能在Open JDK中使用，Oracle JDK无法使用</td>
</tr>
<tr>
<td align="left">ZGC</td>
<td>-XX:+UseZGC</td>
<td>使用ZGC，这是个实验参数，需用-XX:+UnlockExperimentalVMOptions解锁试验参数后，才能使用该参数；</td>
</tr>
<tr>
<td align="left">Epsilon</td>
<td>-XX:+UseEpsilonGC</td>
<td>使用EpsilonGC，这是个实验参数，需用-XX:+UnlockExperimentalVMOptions解锁试验参数后，才能使用该参数；</td>
</tr>
</tbody></table>
<p>引用：</p>
<p><a target="_blank" rel="noopener" href="https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/index.html">Getting Started with the G1 Garbage Collector</a></p>
<p><a target="_blank" rel="noopener" href="https://chriswhocodes.com/">JVM各个发行版的JVM参数</a></p>

      
    </div>

    
    
    

    <footer class="post-footer">
        <div class="post-eof"></div>
      
    </footer>
  </article>
</div>




  <nav class="pagination">
    <span class="page-number current">1</span><a class="page-number" href="/page/2/">2</a><span class="space">&hellip;</span><a class="page-number" href="/page/24/">24</a><a class="extend next" rel="next" href="/page/2/"><i class="fa fa-angle-right" aria-label="下一页"></i></a>
  </nav>


<script>
  window.addEventListener('tabs:register', () => {
    let { activeClass } = CONFIG.comments;
    if (CONFIG.comments.storage) {
      activeClass = localStorage.getItem('comments_active') || activeClass;
    }
    if (activeClass) {
      const activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
      if (activeTab) {
        activeTab.click();
      }
    }
  });
  if (CONFIG.comments.storage) {
    window.addEventListener('tabs:click', event => {
      if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
      const commentClass = event.target.classList[1];
      localStorage.setItem('comments_active', commentClass);
    });
  }
</script>
</div>
  </main>

  <footer class="footer">
    <div class="footer-inner">


<div class="copyright">
  &copy; 
  <span itemprop="copyrightYear">2022</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">一年春又来</span>
</div>
  <div class="powered-by">由 <a href="https://hexo.io/" class="theme-link" rel="noopener" target="_blank">Hexo</a> & <a href="https://theme-next.js.org/mist/" class="theme-link" rel="noopener" target="_blank">NexT.Mist</a> 强力驱动
  </div>

    </div>
  </footer>

  
  <script src="https://cdn.jsdelivr.net/npm/animejs@3.2.1/lib/anime.min.js"></script>
<script src="/js/utils.js"></script><script src="/js/motion.js"></script><script src="/js/schemes/muse.js"></script><script src="/js/next-boot.js"></script>

  
<script src="/js/local-search.js"></script>






  





</body>
</html>
